<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>코딩스타트</title>
    <link>https://coding-start.tistory.com/</link>
    <description>코딩초보를 위한 코딩초보의 블로깅</description>
    <language>ko</language>
    <pubDate>Tue, 26 May 2026 05:12:15 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>여성게</managingEditor>
    <image>
      <title>코딩스타트</title>
      <url>https://tistory1.daumcdn.net/tistory/2876244/attach/7179420edbb84ea99190149b0cd0bdb5</url>
      <link>https://coding-start.tistory.com</link>
    </image>
    <item>
      <title>DBMS - 데이터베이스 락(lock) 종류와 트랜잭션 격리수준(isolation level)</title>
      <link>https://coding-start.tistory.com/446</link>
      <description>&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1600&quot; data-origin-height=&quot;840&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJX3ma/btsLUUtQpiO/OPcBOBMtb9hBwk6vG3K080/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJX3ma/btsLUUtQpiO/OPcBOBMtb9hBwk6vG3K080/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJX3ma/btsLUUtQpiO/OPcBOBMtb9hBwk6vG3K080/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJX3ma%2FbtsLUUtQpiO%2FOPcBOBMtb9hBwk6vG3K080%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1600&quot; height=&quot;840&quot; data-origin-width=&quot;1600&quot; data-origin-height=&quot;840&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;RDBMS에서 데이터를 일관성을 위한 동기화(Lock)을 설정하는 방법은 크게 2가지로 나뉜다. 하나는 비관적 락, 나머지 하나는 낙관적 락이다. 아래는 각각에 대한 개략적인 설명이다.&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;Pessimistic lock(비관적 락)&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; 비관적 락이란 트랜잭션이 시작될 때 데이터의 행에 Shared Lock 또는 Exclusive Lock을 걸고 시작하는 방법이다.&lt;/b&gt;&lt;span style=&quot;color: #555555;&quot;&gt; 즉, write를 하기위해서는 Exclucive Lock을 얻어야하는데 Shared Lock이 다른 트랜잭션에 의해서 걸려 있으면 해당 Lock을 얻지 못해서 업데이트를 할 수 없다 수정을 하기 위해서는 shared lock을 설정한 트랜잭션이 종료(commit or rollback)된 후 exclusive lock을 설정해야한다.&lt;/span&gt;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt; 
 &lt;li&gt;shared lock(s-lock) 
  &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;/li&gt; 
 &lt;li&gt;exclusive lock(x-lock) 
  &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;/li&gt; 
 &lt;li&gt;update lock(u-lock) 
  &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;/li&gt; 
 &lt;li&gt;intent lock(i-lock) 
  &lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt; 
   &lt;li&gt;목적: 상위 객체(ex. 테이블)에 락을 설정하기 전에 하위 객체(ex. 행)에 대한 락 의도를 명시하여 락 설정 여부를 빠르게 판단할 수 있도록함.&lt;/li&gt; 
   &lt;li&gt;특징: 의도 공유 락(IS), 의도 배타 락(IX) 등이 있으며, 상위 수준의 락 설정시 충돌 방지&lt;/li&gt; 
  &lt;/ul&gt; &lt;/li&gt; 
&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1046&quot; data-origin-height=&quot;621&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mgj7k/btsLWN06sW2/q60YrG6BcWuWqR4ULkSX4K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mgj7k/btsLWN06sW2/q60YrG6BcWuWqR4ULkSX4K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mgj7k/btsLWN06sW2/q60YrG6BcWuWqR4ULkSX4K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fmgj7k%2FbtsLWN06sW2%2Fq60YrG6BcWuWqR4ULkSX4K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1046&quot; height=&quot;621&quot; data-origin-width=&quot;1046&quot; data-origin-height=&quot;621&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;비관적 락의 트랜잭션 격리수준&lt;/h3&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt; 
 &lt;li&gt;read uncommitted 
  &lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt; 
   &lt;li&gt;데이터를 읽을때, shared lock을 사용하지 않기 때문에 exclusive lock이 설정된 데이터도 읽을 수 있음.&lt;/li&gt; 
   &lt;li&gt;dirty read: 한 트랜잭션이 커밋되지 않은 데이터를 다른 트랜잭션이 읽을 수 있어, 해당 트랜잭션이 롤백되면 잘못된 데이터를 읽게 될 수 있음.&lt;/li&gt; 
  &lt;/ul&gt; &lt;/li&gt; 
 &lt;li&gt;read committed 
  &lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt; 
   &lt;li&gt;데이터를 읽을때, shared lock을 설정하기 때문에 다른 트랜잭션에서 데이터를 수정할 수 없음.&lt;/li&gt; 
   &lt;li&gt;non-repeatable read: 하나의 트랜잭션 내에서 동일한 데이터를 두 번 읽을 때, 중간에 다른 트랜잭션이 해당 데이터를 수정하고 커밋하면 처음과 다른 결과를 얻을 수 있음.&lt;/li&gt; 
  &lt;/ul&gt; &lt;/li&gt; 
 &lt;li&gt;repeatable read 
  &lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt; 
   &lt;li&gt;데이터를 읽을 때 설정한 shared lock이 트랜잭션이 종료될 때까지 유지하여 해당 트랜잭션이 끝나기 전까지 해당 데이터를 수정하지 못함.&lt;/li&gt; 
   &lt;li&gt;phantom read: 트랜잭션이 범위 쿼리를 수행한 후, 다른 트랜잭션이 해당 범위 내에 새로운 행을 삽입하거나 삭제하면, 처음 쿼리와 동일한 범위 쿼리를 다시 수행할 때 결과 집합이 달라질 수 있음.&lt;/li&gt; 
  &lt;/ul&gt; &lt;/li&gt; 
 &lt;li&gt;serializable 
  &lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt; 
   &lt;li&gt;범위 쿼리를 수행할 때 해당 범위에 shared lock을 설정하여 다른 트랜잭션이 해당 범위 내에서 데이터를 삽입, 수정, 삭제하지 못하도록함.&lt;/li&gt; 
   &lt;li&gt;dead lock, low performance&lt;/li&gt; 
  &lt;/ul&gt; &lt;/li&gt; 
&lt;/ul&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;303&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c5G0Sq/btsLWQjcS7k/U9KGuQMBkD6xOVqCT2sEQ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c5G0Sq/btsLWQjcS7k/U9KGuQMBkD6xOVqCT2sEQ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c5G0Sq/btsLWQjcS7k/U9KGuQMBkD6xOVqCT2sEQ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc5G0Sq%2FbtsLWQjcS7k%2FU9KGuQMBkD6xOVqCT2sEQ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;303&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;303&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;트랜잭션 격리 수준은 trade-off가 필요한 결정사항이고 서비스 요구수준에 맞춘 격리 수준을 택하는 것이 필요할 듯하다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1400&quot; data-origin-height=&quot;756&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bXrUuF/btsLWcG3C8L/uJSZ2U6dapOLARGVnMFql1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bXrUuF/btsLWcG3C8L/uJSZ2U6dapOLARGVnMFql1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bXrUuF/btsLWcG3C8L/uJSZ2U6dapOLARGVnMFql1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbXrUuF%2FbtsLWcG3C8L%2FuJSZ2U6dapOLARGVnMFql1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1400&quot; height=&quot;756&quot; data-origin-width=&quot;1400&quot; data-origin-height=&quot;756&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;Optimistic lock(낙관적 락)&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;낙관적 락&lt;/b&gt;&lt;span style=&quot;color: #555555;&quot;&gt;은 DB 충돌 상황을 개선할 수 있는 방법 중 2번째인 수정할 때 내가 먼저 이 값을 수정했다고 명시하여 다른 사람이 동일한 조건으로 값을 수정할 수 없게 하는 것이다. 그런데 잘 보면 이 특징은&lt;/span&gt;&lt;span style=&quot;color: #555555;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;DB에서 제공해주는 특징을 이용하는 것이 아닌 Application Level에서 잡아주는 Lock이다.&lt;/b&gt;&lt;br&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;충돌은 잘 발생하지 않을 것이라는 전제로 데이터의 일관성을 맞추는 방법&amp;nbsp;&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;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;530&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqDV9Z/btsLYuf7UsH/bZfClemsouj0LTueKx01q1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqDV9Z/btsLYuf7UsH/bZfClemsouj0LTueKx01q1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqDV9Z/btsLYuf7UsH/bZfClemsouj0LTueKx01q1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbqDV9Z%2FbtsLYuf7UsH%2FbZfClemsouj0LTueKx01q1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;530&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;530&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;ol style=&quot;list-style-type: decimal; color: #555555; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt; 
 &lt;li style=&quot;list-style-type: decimal; color: #666666;&quot;&gt;A가 table의 Id 2번을 읽음 ( name = Karol, version = 1 )&lt;/li&gt; 
 &lt;li style=&quot;list-style-type: decimal; color: #666666;&quot;&gt;B가 table의 Id 2번을 읽음 ( name = Karol, version = 1 )&lt;/li&gt; 
 &lt;li style=&quot;list-style-type: decimal; color: #666666;&quot;&gt;B가 table의 Id 2번, version 1인 row의 값 갱신 ( name = Karol2, version = 2 ) 성공&lt;/li&gt; 
 &lt;li style=&quot;list-style-type: decimal; color: #666666;&quot;&gt;A가 table의 Id 2번, version 1인 row의 값 갱신 ( name = Karol1, version = 2 ) 실패 
  &lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt; 
   &lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;&lt;b&gt;Id 2번은 이미 version이 2로 업데이트 되었기 때문에 A는 해당 row를 갱신하지 못함&lt;/b&gt;&lt;/li&gt; 
  &lt;/ul&gt; &lt;/li&gt; 
&lt;/ol&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: justify;&quot;&gt;위 flow를 통해서&amp;nbsp;&lt;b&gt;같은 row에 대해서 각기 다른 2개의 수정 요청이 있었지만 1개가 업데이트 됨에 따라 version이 변경되었기 때문에 뒤의 수정 요청은 반영되지 않는다.&lt;/b&gt;&amp;nbsp;이렇게 낙관적락은 version과 같은 별도의 컬럼을 추가하여 충돌적인 업데이트를 막는다. version 뿐만 아니라 hashcode 또는 timestamp를 이용하기도 한다.&lt;/p&gt;</description>
      <category>Web/Spring</category>
      <category>exclusive lock</category>
      <category>MYSQL</category>
      <category>Optimistic Lock</category>
      <category>Pessimistic Lock</category>
      <category>shared lock</category>
      <category>Transaction</category>
      <category>격리수준</category>
      <category>낙관적 락</category>
      <category>비관적 락</category>
      <category>트랜잭션</category>
      <author>여성게</author>
      <guid isPermaLink="true">https://coding-start.tistory.com/446</guid>
      <comments>https://coding-start.tistory.com/446#entry446comment</comments>
      <pubDate>Wed, 22 Jan 2025 16:24:56 +0900</pubDate>
    </item>
    <item>
      <title>Spring - lettuce redis mget 동작 방식 설명</title>
      <link>https://coding-start.tistory.com/445</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://brunch.co.kr/@springboot/359&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://brunch.co.kr/@springboot/359&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1736834187911&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;레디스 클러스터  Mget 명령은 어떻게 동작하는가?&quot; data-og-description=&quot;스프링부트, Lettuce 를 사용해서 Mget 동작방식 분석 | 이 글에서는, 레디스 클러스터 환경에서&amp;nbsp;Mget 명령으로 다수키 조회가 어떻게 동작하는지 검토한다. 스프링부트 및 Lettuce 라이브러리를 사용&quot; data-og-host=&quot;brunch.co.kr&quot; data-og-source-url=&quot;https://brunch.co.kr/@springboot/359&quot; data-og-url=&quot;https://brunch.co.kr/@springboot/359&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/Fqlbs/hyX0k0swx8/wOozngmkAkwIgE0JyTV7G1/img.png?width=540&amp;amp;height=481&amp;amp;face=0_0_540_481,https://scrap.kakaocdn.net/dn/yITup/hyX0tJR8Mc/cxwXS6bX80SHdVG0vTYUC0/img.png?width=500&amp;amp;height=500&amp;amp;face=0_0_500_500,https://scrap.kakaocdn.net/dn/cChLsg/hyX0niBn15/C3gYq76VdftN4DiPBnDZA0/img.png?width=1280&amp;amp;height=937&amp;amp;face=0_0_1280_937&quot;&gt;&lt;a href=&quot;https://brunch.co.kr/@springboot/359&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://brunch.co.kr/@springboot/359&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/Fqlbs/hyX0k0swx8/wOozngmkAkwIgE0JyTV7G1/img.png?width=540&amp;amp;height=481&amp;amp;face=0_0_540_481,https://scrap.kakaocdn.net/dn/yITup/hyX0tJR8Mc/cxwXS6bX80SHdVG0vTYUC0/img.png?width=500&amp;amp;height=500&amp;amp;face=0_0_500_500,https://scrap.kakaocdn.net/dn/cChLsg/hyX0niBn15/C3gYq76VdftN4DiPBnDZA0/img.png?width=1280&amp;amp;height=937&amp;amp;face=0_0_1280_937');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;레디스 클러스터 Mget 명령은 어떻게 동작하는가?&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;스프링부트, Lettuce 를 사용해서 Mget 동작방식 분석 | 이 글에서는, 레디스 클러스터 환경에서&amp;nbsp;Mget 명령으로 다수키 조회가 어떻게 동작하는지 검토한다. 스프링부트 및 Lettuce 라이브러리를 사용&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;brunch.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>hashslot</category>
      <category>lettuce</category>
      <category>mget</category>
      <category>multiget</category>
      <category>Redis</category>
      <category>redis cluster</category>
      <category>slot</category>
      <category>Spring</category>
      <author>여성게</author>
      <guid isPermaLink="true">https://coding-start.tistory.com/445</guid>
      <comments>https://coding-start.tistory.com/445#entry445comment</comments>
      <pubDate>Tue, 14 Jan 2025 14:56:47 +0900</pubDate>
    </item>
    <item>
      <title>Java - Virtual thread(가상 쓰레드)</title>
      <link>https://coding-start.tistory.com/443</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1211&quot; data-origin-height=&quot;528&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/78RPJ/btsLe07gRNh/GYX8jW4Nt6GY4ePuYUcGJK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/78RPJ/btsLe07gRNh/GYX8jW4Nt6GY4ePuYUcGJK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/78RPJ/btsLe07gRNh/GYX8jW4Nt6GY4ePuYUcGJK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F78RPJ%2FbtsLe07gRNh%2FGYX8jW4Nt6GY4ePuYUcGJK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1211&quot; height=&quot;528&quot; data-origin-width=&quot;1211&quot; data-origin-height=&quot;528&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;오늘 알아볼 주제는 JDK 21부터 정식으로 도입된 Virtual Thread이다. 불가 얼마전까지만 해도 Webflux나 Kotlin의 Coroutine을 이용해 애플리케이션을 개발하였는데, 최근에 Virtual Thread를 도입하면서 조금더 깊은 이해를 가지고 사용하는 것이 좋다 생각하여 정리해본다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;우선 Virtual Thread를 알아보기전에 Java의 전통적인 Thread 모델에 대해 다시한번 되짚어보자.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;Thread란?&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;639&quot; data-origin-height=&quot;321&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UCUAh/btsLdw7GmiT/cqw0THd1ehJJdEUEkoXMDK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UCUAh/btsLdw7GmiT/cqw0THd1ehJJdEUEkoXMDK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UCUAh/btsLdw7GmiT/cqw0THd1ehJJdEUEkoXMDK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUCUAh%2FbtsLdw7GmiT%2Fcqw0THd1ehJJdEUEkoXMDK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;639&quot; height=&quot;321&quot; data-origin-width=&quot;639&quot; data-origin-height=&quot;321&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;위 그림과 같이 우선 Thread에는 유저 레벨 쓰레드, 커널 레벨 쓰레드, 혼합형 쓰레드가 있다.&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&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;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;사용자 레벨 쓰레드에서는 쓰레드 context switcing에 커널이 개입하지 않아 커널에서 사용자 영역으로 전환할 필요가 없다. 그리고 커널은 쓰레드가 아닌 프로세스를 한 단위로 안식하고 프로세서를 할당한다.&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;동일한 메모리 영역에서 스레드가 생성 및 관리되므로 속도가 빠른 장점이 있는 반면,&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;여러 개의 사용자 스레드 중 하나의 스레드가 시스템 호출(system call) 등으로 중단되면 나머지 모든 스레드 역시 중단되는 단점이 있다. 이는 커널이 프로세스 내부의 스레드를 인식하지 못하며 해당 프로세스를 대기 상태로 전환시키기 때문이다.&lt;/b&gt;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;커널 레벨 쓰레드&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커널 스레드는 사용자 레벨 쓰레드의 한계를 극복하는 방법으로, 커널이 쓰레드와 관련된 모든 작업을 관리한다.(PCB, TCB 유지) 한 프로세스에서 다수의 쓰레드가 프로세서를 할당받아 병행으로 수행하고, 쓰레드 한 개가 대기 상태가 되면 동일한 프로세스에 속한 다른 쓰레드로  context switching이 가능하다. 이때 커널이 개입하므로 사용자 영역에서 커널 영역으로 전환이 필요하다. 위 그림처럼 유저 레벨 쓰레드 하나를 생성할때 커널 쓰레드가 1:1로 매핑된다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;운영체제가 지원하는 스레드 기능으로 구현되며, 커널이 스레드의 생성 및 스케줄링 등을 관리한다. &lt;b&gt;스레드가 시스템 호출(system call) 등으로 중단되더라도, 커널은 프로세스 내의 다른 스레드를 중단시키지 않고 계속 실행시켜준다.&lt;/b&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;멀티프로세싱&lt;/b&gt;&lt;/span&gt;&lt;b&gt;&amp;nbsp;환경에서 커널은 여러 개의 스레드를 각각 다른 프로세서에 할당할 수 있다.&lt;/b&gt; 다만, 사용자 스레드에 비해 생성 및 관리하는 것이 느리다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;혼합형 쓰레드&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혼합형 쓰레드는 사용자 레벨 쓰레드와 커널 수준 쓰레드를 혼합한 구조이다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;그렇다면 자바는 어떤 쓰레드를 사용할까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 여러 글을 찾아봐도 100% 해답을 찾지 못했지만, 결론적으로 리눅스에선 Java는 1:1 매핑의 커널 레벨 쓰레드 모델을 사용하고 있는 것으로 보인다.(이미 생성된 커널쓰레드가 놀고 있다면 자바 쓰레드와 unmount되고 새로 생성된 자바 쓰래드와 1:1 매핑되는것 같다. 즉, 실행 시점에 유저쓰레드:커널 쓰레드 = 1:1이 맞는 표현 같다.) 실제로 Java의 Thread 코드를 보면 Thread.start() 시점에 native method(c++로 작성된)를 사용하여 커널 레벨 쓰레드를 생성하고 Java의 Thread local variable에 커널 레벨 쓰레드를 멤버 변수가 가지고 있는다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;다음으로는 Java에서 I/O가 발생했을 때 쓰레드의 상태를 알아보자.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;전통적인 Java Thread I/O&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java의 Thread에서 I/O(ex. file read/write, network i/o)가 발생하면, Thread는 system call을 하게된다. system call을 하게 되면 유저 영역에서 커널 영역으로 전환이 되고 커널 쓰레드는 요청온 I/O 작업을 진행하게 된다.(해당 과정에서 인터럽트가 일어나 CPU에게 I/O 작업이 있다고 알린다.) 이때 커널 쓰레드는 Block 상태가 되고, 그에 따라 유저 레벨의 Java Thread도 아무일도 하지 못한채 Block이 되고 만다. 여기서 Java는 커널 레벨 쓰레드 모델을 사용하기 때문에 CPU는 I/O 요청이 온 쓰레드를 대기 큐에 넣고, 준비 큐에 있는 다른 쓰레드의 작업을 진행하게 된다.(context switching 발생) I/O 작업이 완료되었다면 CPU에게 인터럽트를 발생시켜 작업이 완료되었다는 것을 알리고 대기 큐에 있던 Block된 Thread의 작업을 제개하게 된다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;여기까지 Java에서 I/O가 발생했을 때는 대략적인 흐름인데, Spring MVC에서는 그 많은 요청을 어떻게 계속 받을 수 있게 되는 것일까? 그것은 보통 우리는 WAS의 servlet Thread pool을 만들어 사용하기 때문에 최소한 pool에 담긴 thread 개수 만큼은 동시에 I/O가 발생하여도 동시 요청을 받을 수 있게 되는 것이다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;하지만 문제는 무엇일까? 바로 자원은 유한하기 때문에 pool의 thread를 무한히 늘릴 수 없는 것이다. 또한 Java의 thread는 커널 레벨의 쓰레드와 1:1 매핑되기 때문에 I/O 블록킹이 발생하면 CPU의 준비큐에 있는 다른 Thread의 작업을 진행하게 위한 Context switching 비용이 비싸기 때문에 무한히 thread를 늘린다 한들 linear하게 성능이 올라가지 않는 것이다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;1. 시스템 콜 호출&lt;/b&gt;: 애플리케이션의 스레드가 파일 읽기나 소켓 통신을 요청하면, 해당 요청은 시스템 콜을 통해 운영 체제의 커널에 전달된다.&lt;br /&gt;&lt;b&gt;2. 커널 모드 전환&lt;/b&gt;: 시스템 콜이 호출되면, 프로세스는 커널 모드로 전환되어 커널이 해당 I/O 작업을 처리할 수 있게 된다.&lt;br /&gt;&lt;b&gt;3. I/O 작업 처리&lt;/b&gt;: 커널은 디바이스 드라이버를 통해 실제 I/O 작업을 수행한다. 이때, 작업의 완료까지 시간이 소요될 수 있.&lt;br /&gt;&lt;b&gt;4. 스레드  context switching&lt;/b&gt;: I/O 작업이 진행되는 동안, 해당 스레드는 BLOCKED 상태로 전환되어 CPU를 다른 작업에 할당할 수 있게 된다.&lt;br /&gt;&lt;b&gt;5. I/O 완료 및 인터럽트 발생&lt;/b&gt;: I/O 작업이 완료되면, 디바이스는 인터럽트를 발생시켜 커널에 작업 완료를 알린다.&lt;br /&gt;&lt;b&gt;6. 스레드 재개&lt;/b&gt;: 커널은 BLOCKED 상태에 있던 스레드를 READY 상태로 전환하고, 스케줄러는 해당 스레드에 CPU를 재할당하여 작업을 이어서 수행할 수 있게 한다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Reactive(Webflux)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 I/O가 발생될 때마다 쓰레드가 블록킹되고 그에 따른 컨텍스트 스위칭에 대한 비용이 크기 때문에 non-blocking reactive 프로그래밍 방식이 등장하였다.(리눅스 환경인 경우 Webflux에서 네트워크 I/O가 발생하면 파일 디스크립터 감시를 위해 epoll을 사용한다.)&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;epoll&lt;br /&gt;epoll은 리눅스에서 대규모 파일 디스크립터를 효율적으로 모니터링하기 위해 설계된 I/O 이벤트 통지 메커니즘입니다.&amp;nbsp;epoll의 동작은 설정에 따라&amp;nbsp;블로킹&amp;nbsp;또는&amp;nbsp;논블로킹&amp;nbsp;모드로 작동할 수 있습니다.&lt;br /&gt;&lt;br /&gt;동작 모드:&lt;br /&gt;1.&amp;nbsp;블로킹 모드:&amp;nbsp;epoll_wait()&amp;nbsp;함수를 호출할 때, 지정된 파일 디스크립터에 이벤트가 발생할 때까지 호출한 스레드는 대기 상태에 머무릅니다. 이때,&amp;nbsp;epoll_wait()는 이벤트가 발생하거나 타임아웃이 도래하면 반환됩니다.&lt;br /&gt;2.&amp;nbsp;논블로킹 모드:&amp;nbsp;epoll_wait()&amp;nbsp;호출 시 타임아웃을 0으로 설정하면, 함수는 즉시 반환하며, 이때 이벤트가 준비되지 않았으면 빈 결과를 반환합니다. 또한, 파일 디스크립터를 논블로킹 모드로 설정하면, I/O 작업이 즉시 완료되지 않더라도 스레드는 블로킹되지 않고 다른 작업을 계속 수행할 수 있습니다.&lt;br /&gt;&lt;br /&gt;레벨 트리거(Level-Triggered)와 엣지 트리거(Edge-Triggered):&lt;br /&gt;&lt;br /&gt;epoll은 두 가지 트리거 모드를 지원합니다:&lt;br /&gt;&amp;bull;&amp;nbsp;레벨 트리거(Level-Triggered): 기본 모드로, 파일 디스크립터가 읽기 또는 쓰기 가능한 상태일 때마다 이벤트를 지속적으로 보고합니다. 이 모드에서는 이벤트를 처리하지 않으면 동일한 이벤트가 반복적으로 전달될 수 있습니다.&lt;br /&gt;&amp;bull;&amp;nbsp;엣지 트리거(Edge-Triggered): 상태의 변화를 감지하여 이벤트를 보고합니다. 예를 들어, 새로운 데이터가 도착했을 때 한 번만 이벤트를 전달하며, 추가적인 데이터가 도착하지 않으면 추가 이벤트는 발생하지 않습니다. 이 모드에서는 I/O 작업을 완료하기 위해 반복적으로 읽기 또는 쓰기 작업을 수행해야 하며, 그렇지 않으면 데이터가 남아있어도 추가 이벤트를 받지 못할 수 있습니다.&lt;br /&gt;&lt;br /&gt;참고사항:&lt;br /&gt;&lt;br /&gt;epoll을 사용할 때, 파일 디스크립터를 논블로킹 모드로 설정하는 것이 일반적입니다. 이를 통해 I/O 작업이 즉시 완료되지 않더라도 스레드가 블로킹되지 않고 다른 작업을 수행할 수 있습니다. 이러한 설정은 높은 동시성을 필요로 하는 서버 애플리케이션에서 효율적인 자원 활용을 가능하게 합니다.&lt;br /&gt;&lt;br /&gt;따라서,&amp;nbsp;epoll의 블로킹 여부는 함수 호출 시 설정한 타임아웃 값과 파일 디스크립터의 모드에 따라 결정됩니다. 적절한 설정을 통해 원하는 동작 방식을 구현할 수 있습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;726&quot; data-origin-height=&quot;257&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cB5Bw9/btsLe2D2GU9/PFRoMJUy4R2BpFqF5qabXK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cB5Bw9/btsLe2D2GU9/PFRoMJUy4R2BpFqF5qabXK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cB5Bw9/btsLe2D2GU9/PFRoMJUy4R2BpFqF5qabXK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcB5Bw9%2FbtsLe2D2GU9%2FPFRoMJUy4R2BpFqF5qabXK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;726&quot; height=&quot;257&quot; data-origin-width=&quot;726&quot; data-origin-height=&quot;257&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;Webflux는 I/O가 발생하여도 쓰레드를 블록킹하지 않기 때문에 적은 비용으로 thread per request 모델보다 더 좋은 성능을 낼 수 있다. 하지만 아래와 같은 단점이 있다.&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;li&gt;디버깅의 어려움: 비동기 흐름으로 인해 전통적인 디버깅 기법이 효과적이지 않을 수 있다.&lt;/li&gt;
&lt;li&gt;코드 복잡성 증가: 비동기 및 리액티브 프로그래밍은 코드의 복잡성을 높이고, 디버깅과 오류 처리를 어렵게 만들 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;Virtual Thread&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #555555;&quot;&gt;기존의 Java 스레드와 Webflux를 알아보았으니 이제 JDK 21에 새로 도입된 Virtual Thread를 알아보자.&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;667&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/F5pJB/btsLdcPhQrz/lb9RdD92UNbx6Y5Lw3vbok/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/F5pJB/btsLdcPhQrz/lb9RdD92UNbx6Y5Lw3vbok/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/F5pJB/btsLdcPhQrz/lb9RdD92UNbx6Y5Lw3vbok/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FF5pJB%2FbtsLdcPhQrz%2Flb9RdD92UNbx6Y5Lw3vbok%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;667&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;667&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;Heap에 존재하는 많은 ULT 중 하나가 JVM의 스케줄링에 따라 KLT에 매핑되어 실행하는 형태가 기존의 Java 스레드 모델이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Virtual Thread concepts&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1175&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d5Faci/btsLdxyI5vm/RuKU3fhAx7ifsdhai0QHFK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d5Faci/btsLdxyI5vm/RuKU3fhAx7ifsdhai0QHFK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d5Faci/btsLdxyI5vm/RuKU3fhAx7ifsdhai0QHFK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd5Faci%2FbtsLdxyI5vm%2FRuKU3fhAx7ifsdhai0QHFK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;1175&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1175&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #777777;&quot;&gt;출처:&lt;/span&gt;&lt;span style=&quot;color: #777777;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://jenkov.com/tutorials/java-concurrency/java-virtual-threads.html&quot; target=&quot;_self&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #777777;&quot;&gt;https://jenkov.com/tutorials/java-concurrency/java-virtual-threads.html&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;Virtual Thread는 기존 KLT(1) : ULT(1)의 구조가 아닌 KLT(1) : ULT(1) : Virtual Thread(N)의 구조로 사용된다. KLT와 Virtual Thread 사이의 ULT는 플랫폼 스레드라고 한다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;위 그림과 같이 Heap에 수많은 Virtual Thread를 할당해놓고, 플랫폼 스레드에 대상 Virtual Thread를 마운트/언마운트하여 컨텍스트 스위칭을 수행한다. 따라서 컨텍스트 스위칭 비용이 작아질 수 밖에 없다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;663&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/djDN4t/btsLd948YWI/MAgidhYGk6Ma1B9N82dW7k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/djDN4t/btsLd948YWI/MAgidhYGk6Ma1B9N82dW7k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/djDN4t/btsLd948YWI/MAgidhYGk6Ma1B9N82dW7k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdjDN4t%2FbtsLd948YWI%2FMAgidhYGk6Ma1B9N82dW7k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;663&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;663&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스레드의 크기와 컨텍스트 스위칭 비용이 많이 감소한 모델이기 때문에 Spring MVC/Tomcat 등의 모델이 Netty/WebFlux에 비해 가진 단점이 많이 희석되었다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Virtual Thread states&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Virtual Thread에는 9개의 상태가 있다.&lt;/p&gt;
&lt;pre class=&quot;gradle&quot; style=&quot;background-color: #f9f9f9; color: #666666; text-align: start;&quot;&gt;&lt;code&gt;    /*
     * Virtual thread state and transitions:
     *
     *      NEW -&amp;gt; STARTED         // Thread.start
     *  STARTED -&amp;gt; TERMINATED      // failed to start
     *  STARTED -&amp;gt; RUNNING         // first run
     *
     *  RUNNING -&amp;gt; PARKING         // Thread attempts to park
     *  PARKING -&amp;gt; PARKED          // cont.yield successful, thread is parked
     *  PARKING -&amp;gt; PINNED          // cont.yield failed, thread is pinned
     *
     *   PARKED -&amp;gt; RUNNABLE        // unpark or interrupted
     *   PINNED -&amp;gt; RUNNABLE        // unpark or interrupted
     *
     * RUNNABLE -&amp;gt; RUNNING         // continue execution
     *
     *  RUNNING -&amp;gt; YIELDING        // Thread.yield
     * YIELDING -&amp;gt; RUNNABLE        // yield successful
     * YIELDING -&amp;gt; RUNNING         // yield failed
     *
     *  RUNNING -&amp;gt; TERMINATED      // done
     */
    private static final int NEW      = 0;
    private static final int STARTED  = 1;
    private static final int RUNNABLE = 2;     // runnable-unmounted
    private static final int RUNNING  = 3;     // runnable-mounted
    private static final int PARKING  = 4;
    private static final int PARKED   = 5;     // unmounted
    private static final int PINNED   = 6;     // mounted
    private static final int YIELDING = 7;     // Thread.yield
    private static final int TERMINATED = 99;  // final state
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #777777;&quot;&gt;출처:&lt;/span&gt;&lt;span style=&quot;color: #777777;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://github.com/openjdk/jdk21/blob/master/src/java.base/share/classes/java/lang/VirtualThread.java#L91&quot; target=&quot;_self&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #777777;&quot;&gt;https://github.com/openjdk/jdk21/blob/master/src/java.base/share/classes/java/lang/VirtualThread.java#L91&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;다음과 같이 Virtual Thread의 상태에 따라 플랫폼 스레드에 마운트/언마운트해 실행을 관리한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1058&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cULsEm/btsLdh3ZMCE/IWmFun2RkHnzm8bPFPVVi1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cULsEm/btsLdh3ZMCE/IWmFun2RkHnzm8bPFPVVi1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cULsEm/btsLdh3ZMCE/IWmFun2RkHnzm8bPFPVVi1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcULsEm%2FbtsLdh3ZMCE%2FIWmFun2RkHnzm8bPFPVVi1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;1058&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1058&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;플랫폼 스레드에 언마운트/마운트할 때에는 park/unpark 메서드를 사용한다.&lt;/p&gt;
&lt;pre class=&quot;dart&quot; style=&quot;background-color: #f9f9f9; color: #666666; text-align: start;&quot;&gt;&lt;code&gt;sealed abstract class BaseVirtualThread extends Thread  
        permits VirtualThread, ThreadBuilders.BoundVirtualThread {

    /**
     * Initializes a virtual Thread.
     *
     * @param name thread name, can be null
     * @param characteristics thread characteristics
     * @param bound true when bound to an OS thread
     */
    BaseVirtualThread(String name, int characteristics, boolean bound) {
        super(name, characteristics, bound);
    }

    /**
     * Parks the current virtual thread until the parking permit is available or
     * the thread is interrupted.
     *
     * The behavior of this method when the current thread is not this thread
     * is not defined.
     */
    abstract void park();

    /**
     * Parks current virtual thread up to the given waiting time until the parking
     * permit is available or the thread is interrupted.
     *
     * The behavior of this method when the current thread is not this thread
     * is not defined.
     */
    abstract void parkNanos(long nanos);

    /**
     * Makes available the parking permit to the given this virtual thread.
     */
    abstract void unpark();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #777777;&quot;&gt;출처:&lt;/span&gt;&lt;span style=&quot;color: #777777;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://github.com/openjdk/jdk21/blob/master/src/java.base/share/classes/java/lang/BaseVirtualThread.java#L30&quot; target=&quot;_self&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #777777;&quot;&gt;https://github.com/openjdk/jdk21/blob/master/src/java.base/share/classes/java/lang/BaseVirtualThread.java#L30&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;위 상태 그림처럼 Virtual Thread의 state를 변경시켜가며 상태를 관리한다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; style=&quot;background-color: #f9f9f9; color: #666666; text-align: start;&quot;&gt;&lt;code&gt;@Override
    void park() {

         ... 생략
        // park on the carrier thread when pinned
        if (!yielded) {
            parkOnCarrierThread(false, 0);
        }
    }

    private void parkOnCarrierThread(boolean timed, long nanos) {
        assert state() == RUNNING;

       ... 생략
        setState(PINNED);  RUNNING -&amp;gt; PINNED 로 전환
       ... 생략

    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #777777;&quot;&gt;출처:&lt;/span&gt;&lt;span style=&quot;color: #777777;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://github.com/openjdk/jdk21/blob/master/src/java.base/share/classes/java/lang/VirtualThread.java#L581&quot; target=&quot;_self&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #777777;&quot;&gt;https://github.com/openjdk/jdk21/blob/master/src/java.base/share/classes/java/lang/VirtualThread.java#L581&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;플랫폼 스레드에 마운트하여 실행하는 unpark 메서드를 보자.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;background-color: #f9f9f9; color: #666666; text-align: start;&quot;&gt;&lt;code&gt;    void unpark() {
       ... 생략
            if (s == PARKED &amp;amp;&amp;amp; compareAndSetState(PARKED, RUNNABLE)) {
                if (currentThread instanceof VirtualThread vthread) {
                    vthread.switchToCarrierThread();
                    try {
                        submitRunContinuation();
                    } finally {
                        switchToVirtualThread(vthread);
                    }
                } else {
                    submitRunContinuation();
                }
            }
... 생략
        }
    }

    private void submitRunContinuation() {
        try {
            scheduler.execute(runContinuation);
        } catch (RejectedExecutionException ree) {
            submitFailed(ree);
            throw ree;
        }
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #777777;&quot;&gt;출처:&lt;/span&gt;&lt;span style=&quot;color: #777777;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://github.com/openjdk/jdk21/blob/master/src/java.base/share/classes/java/lang/VirtualThread.java#L733&quot; target=&quot;_self&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #777777;&quot;&gt;https://github.com/openjdk/jdk21/blob/master/src/java.base/share/classes/java/lang/VirtualThread.java#L733&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;보다시피 scheduler로 실제 실행을 넘기며, scheduler는 ForkJoinPool이다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot; style=&quot;background-color: #f9f9f9; color: #666666; text-align: start;&quot;&gt;&lt;code&gt;    private static ForkJoinPool createDefaultScheduler() {
        ForkJoinWorkerThreadFactory factory = pool -&amp;gt; {
            PrivilegedAction&amp;lt;ForkJoinWorkerThread&amp;gt; pa = () -&amp;gt; new CarrierThread(pool);
            return AccessController.doPrivileged(pa);
        };

... 생략
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #777777;&quot;&gt;출처:&lt;/span&gt;&lt;span style=&quot;color: #777777;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://github.com/openjdk/jdk21/blob/master/src/java.base/share/classes/java/lang/VirtualThread.java#L1113&quot; target=&quot;_self&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #777777;&quot;&gt;https://github.com/openjdk/jdk21/blob/master/src/java.base/share/classes/java/lang/VirtualThread.java#L1113&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;Virtual Thread는 플랫폼 스레드를 참조하고 있으며 이는 carrierThread라고 한다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot; style=&quot;background-color: #f9f9f9; color: #666666; text-align: start;&quot;&gt;&lt;code&gt;    // carrier thread when mounted, accessed by VM
    private volatile Thread carrierThread;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #777777;&quot;&gt;출처:&lt;/span&gt;&lt;span style=&quot;color: #777777;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://github.com/openjdk/jdk21/blob/master/src/java.base/share/classes/java/lang/VirtualThread.java#L131&quot; target=&quot;_self&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #777777;&quot;&gt;https://github.com/openjdk/jdk21/blob/master/src/java.base/share/classes/java/lang/VirtualThread.java#L131&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;즉, JVM이 직접 접근하는 스레드는 플랫폼 스레드이며, 플랫폼 스레드에 마운트하여 실행하는 과정은 carrierThread에 실행 대상 Virtual Thread를 할당하는 방식이다.&lt;/p&gt;
&lt;pre class=&quot;lasso&quot; style=&quot;background-color: #f9f9f9; color: #666666; text-align: start;&quot;&gt;&lt;code&gt;    private void mount() {
         ... 생략
        carrier.setCurrentThread(this);     -&amp;gt; 플랫폼 스레드에 실행할 Virtual Thread 할당
         ... 생략
    }

    private void unmount() {

        Thread carrier = this.carrierThread;
        carrier.setCurrentThread(carrier);

        synchronized (interruptLock) {
            setCarrierThread(null);         -&amp;gt; Virtual Thread에서 Virtual Thread 제거
        }
        carrier.clearInterrupt();
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #777777;&quot;&gt;출처:&lt;/span&gt;&lt;span style=&quot;color: #777777;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://github.com/openjdk/jdk21/blob/master/src/java.base/share/classes/java/lang/VirtualThread.java#L351&quot; target=&quot;_self&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #777777;&quot;&gt;https://github.com/openjdk/jdk21/blob/master/src/java.base/share/classes/java/lang/VirtualThread.java#L351&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;Virtual Thread는 플랫폼 스레드를 참조하고 있으며 실제 실행 시에는 플랫폼 스레드에 마운트되어 ForkJoinPool의 큐에 들어가 스케줄링된다.&lt;/p&gt;
&lt;pre class=&quot;gradle&quot; style=&quot;background-color: #f9f9f9; color: #666666; text-align: start;&quot;&gt;&lt;code&gt;private &amp;lt;T&amp;gt; ForkJoinTask&amp;lt;T&amp;gt; poolSubmit(boolean signalIfEmpty,  
                                          ForkJoinTask&amp;lt;T&amp;gt; task) {
       WorkQueue q; Thread t; ForkJoinWorkerThread wt;
       U.storeStoreFence();  // ensure safely publishable
       if (task == null) throw new NullPointerException();
       if (((t = Thread.currentThread()) instanceof ForkJoinWorkerThread) &amp;amp;&amp;amp;
           (wt = (ForkJoinWorkerThread)t).pool == this)
           q = wt.workQueue;
       else {
           task.markPoolSubmission();
           q = submissionQueue(true);
       }
       q.push(task, this, signalIfEmpty);
       return task;
   }
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Virtual Thread pinning&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Virtual Thread의 장점은, JVM이 자체적으로 Virtual Thread를 스케줄링하고 컨텍스트 스위칭 비용이 줄어들어 효율적으로 운영할 수 있다는 것이다. 하지만 Virtual Thread가 플랫폼 스레드에 고정되어 장점을 활용할 수 없는 경우가 있다. Virtual Thread 내에서 synchronized block을 사용하거나, JNI를 통해 네이티브 메서드를 사용하는 경우다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;443&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cU1Dq5/btsLdXqlGi8/rEdf9TAssxCZiM8cAMXgl0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cU1Dq5/btsLdXqlGi8/rEdf9TAssxCZiM8cAMXgl0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cU1Dq5/btsLdXqlGi8/rEdf9TAssxCZiM8cAMXgl0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcU1Dq5%2FbtsLdXqlGi8%2FrEdf9TAssxCZiM8cAMXgl0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;443&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;443&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #777777;&quot;&gt;출처:&lt;/span&gt;&lt;span style=&quot;color: #777777;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://docs.oracle.com/en/java/javase/21/core/virtual-threads.html#GUID-704A716D-0662-4BC7-8C7F-66EE74B1EDAD&quot; target=&quot;_self&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #777777;&quot;&gt;https://docs.oracle.com/en/java/javase/21/core/virtual-threads.html#GUID-704A716D-0662-4BC7-8C7F-66EE74B1EDAD&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;Virtual Thread는 Spring Boot 3.2.x에서 공식적으로 지원하지만(&lt;a href=&quot;https://spring.io/blog/2023/09/09/all-together-now-spring-boot-3-2-graalvm-native-images-java-21-and-virtual&quot; target=&quot;_self&quot;&gt;&lt;span&gt;https://spring.io/blog/2023/09/09/all-together-now-spring-boot-3-2-graalvm-native-images-java-21-and-virtual&lt;/span&gt;&lt;/a&gt;) 2.x에서도 별도로 설정해서 사용할 수 있다(&lt;a href=&quot;https://spring.io/blog/2022/10/11/embracing-virtual-threads&quot; target=&quot;_self&quot;&gt;&lt;span&gt;https://spring.io/blog/2022/10/11/embracing-virtual-threads&lt;/span&gt;&lt;/a&gt;).&lt;/p&gt;
&lt;pre class=&quot;haxe&quot; style=&quot;background-color: #f9f9f9; color: #666666; text-align: start;&quot;&gt;&lt;code&gt; @Bean
    public TomcatProtocolHandlerCustomizer&amp;lt;?&amp;gt; protocolHandlerVirtualThreadExecutorCustomizer() {
        return protocolHandler -&amp;gt; {
            protocolHandler.setExecutor(Executors.newVirtualThreadPerTaskExecutor());
        };
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;다만, 공식 블로그에 따르면 Spring 로직 내에 많은 synchronized가 있어 효율이 좋지 않다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;576&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UHoek/btsLeuVoNpt/JQROKNatq786SWoWb77Xm1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UHoek/btsLeuVoNpt/JQROKNatq786SWoWb77Xm1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UHoek/btsLeuVoNpt/JQROKNatq786SWoWb77Xm1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUHoek%2FbtsLeuVoNpt%2FJQROKNatq786SWoWb77Xm1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;576&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;576&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #777777;&quot;&gt;출처:&lt;/span&gt;&lt;span style=&quot;color: #777777;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://spring.io/blog/2022/10/11/embracing-virtual-threads#mitigating-limitations&quot; target=&quot;_self&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #777777;&quot;&gt;https://spring.io/blog/2022/10/11/embracing-virtual-threads#mitigating-limitations&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;실제로 Spring Boot 2.7.17에서 Virtual Thread를 사용하도록 설정하고&amp;nbsp;-Djdk.tracePinnedThreads=short&amp;nbsp;옵션과 함께 구동한 후 synchronized를 사용하는 컨트롤러를 호출하면 다음과 같은 로그를 많이 볼 수 있다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #f9f9f9; color: #666666; text-align: start;&quot;&gt;&lt;code&gt;    @GetMapping(&quot;/test&quot;)
    @Operation(summary = &quot;테스트&quot;, description = &quot;테스트&quot;)
    public String test() throws Exception {
        synchronized (this){
            Thread.sleep(1000l);
            log.info(&quot;HELLO&quot;);
        }
        return &quot;OK&quot;;
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;angelscript&quot; style=&quot;background-color: #f9f9f9; color: #666666; text-align: start;&quot;&gt;&lt;code&gt;Thread[#185,ForkJoinPool-1-worker-1,5,CarrierThreads]  
    com.example.test.TestController.test(TestController.java:22) &amp;lt;== monitors:1
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;또한 Spring 구동 시 다음과 같은 로그도 볼 수 있다.&lt;/p&gt;
&lt;pre class=&quot;stylus&quot; style=&quot;background-color: #f9f9f9; color: #666666; text-align: start;&quot;&gt;&lt;code&gt;Thread[#184,ForkJoinPool-1-worker-2,5,CarrierThreads]  
    com.mysql.cj.protocol.ReadAheadInputStream.read(ReadAheadInputStream.java:180) &amp;lt;== monitors:1
    com.mysql.cj.jdbc.ConnectionImpl.commit(ConnectionImpl.java:791) &amp;lt;== monitors:1
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;MySQL 패키지에 사용된 synchronized가 pinning을 유발하고 있는 것이다.(최신 spring 3.4.x 버전부터는 mysql의 버전이 pinning관련된 이슈가 해결된 버전이다.)&lt;br /&gt;&amp;nbsp;&lt;br /&gt;따라서 Spring은 synchronized를 ReentrantLock으로 마이그레이션하는 방향으로 가고 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;736&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bktdqX/btsLdHuylgn/NteaKqmRNPt0RkEl9ka1E0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bktdqX/btsLdHuylgn/NteaKqmRNPt0RkEl9ka1E0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bktdqX/btsLdHuylgn/NteaKqmRNPt0RkEl9ka1E0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbktdqX%2FbtsLdHuylgn%2FNteaKqmRNPt0RkEl9ka1E0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;736&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;736&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #777777;&quot;&gt;출처:&lt;/span&gt;&lt;span style=&quot;color: #777777;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-3.2.0-M2-Release-Notes#support-for-virtual-threads&quot; target=&quot;_self&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #777777;&quot;&gt;https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-3.2.0-M2-Release-Notes#support-for-virtual-threads&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;그 밖에도 많은 진영에서 Virtual Thread를 지원하기 위해 synchronized에서 ReentrantLock으로 마이그레이션이 진행되고 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;MySQL:&amp;nbsp;&lt;a href=&quot;https://github.com/mysql/mysql-connector-j/pull/95&quot; target=&quot;_self&quot;&gt;&lt;span&gt;https://github.com/mysql/mysql-connector-j/pull/95&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;UUId:&amp;nbsp;&lt;a href=&quot;https://github.com/f4b6a3/uuid-creator/commit/3e684b1dec472b51a641bbd1762b33c9ea62bc77&quot; target=&quot;_self&quot;&gt;&lt;span&gt;https://github.com/f4b6a3/uuid-creator/commit/3e684b1dec472b51a641bbd1762b33c9ea62bc77&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;synchronized가 많이 남아있는 Spring Boot 2.x에서는 Virtual Thread를 잘 사용하기 위해서는 여러 의존 모듈의 마이그레이션이 선행되어야 할 것 같다. 앞으로 미래 Java 버전에서는 synchronized는 점점 사라질 것으로 예상한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Virtual Thread blocking&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 Java 스레드는 sleep 실행 시 blocking 상태가 되며 다른 스레드와 컨텍스트 스위칭을 한다. Virtual Thread의 sleep을 살펴보자.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;background-color: #f9f9f9; color: #666666; text-align: start;&quot;&gt;&lt;code&gt;    public static void sleep(long millis) throws InterruptedException {
        if (millis &amp;lt; 0) {
            throw new IllegalArgumentException(&quot;timeout value is negative&quot;);
        }

        long nanos = MILLISECONDS.toNanos(millis);
        ThreadSleepEvent event = beforeSleep(nanos);
        try {
            if (currentThread() instanceof VirtualThread vthread) {
                vthread.sleepNanos(nanos);
            } else {
                sleep0(nanos);
            }
        } finally {
            afterSleep(event);
        }
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #777777;&quot;&gt;출처:&lt;/span&gt;&lt;span style=&quot;color: #777777;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://github.com/openjdk/jdk21/blob/master/src/java.base/share/classes/java/lang/Thread.java#L498&quot; target=&quot;_self&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #777777;&quot;&gt;https://github.com/openjdk/jdk21/blob/master/src/java.base/share/classes/java/lang/Thread.java#L498&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;기존 스레드의 경우 sleep0 JNI 호출로 KLT와 함께 block 상태로 변경되고 Virtual Thread의 경우 다른 동작을 하는 것을 볼 수 있다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;background-color: #f9f9f9; color: #666666; text-align: start;&quot;&gt;&lt;code&gt;    void sleepNanos(long nanos) throws InterruptedException {
... 생략
                    parkNanos(remainingNanos);
... 생략
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #777777;&quot;&gt;출처:&lt;/span&gt;&lt;span style=&quot;color: #777777;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://github.com/openjdk/jdk21/blob/master/src/java.base/share/classes/java/lang/VirtualThread.java#L791&quot; target=&quot;_self&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #777777;&quot;&gt;https://github.com/openjdk/jdk21/blob/master/src/java.base/share/classes/java/lang/VirtualThread.java#L791&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;background-color: #f9f9f9; color: #666666; text-align: start;&quot;&gt;&lt;code&gt;    @Override
void parkNanos(long nanos) {  
... 생략
  boolean yielded = false;
  Future&amp;lt;?&amp;gt; unparker = scheduleUnpark(this::unpark, nanos);
  setState(PARKING);
  try {
    yielded = yieldContinuation();  // may throw
... 생략
  }

  private boolean yieldContinuation() {
    // unmount
    notifyJvmtiUnmount(/*hide*/true);
    unmount();
    try {
      return Continuation.yield(VTHREAD_SCOPE);
    } finally {
      // re-mount
      mount();
      notifyJvmtiMount(/*hide*/false);
    }
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #777777;&quot;&gt;출처:&lt;/span&gt;&lt;span style=&quot;color: #777777;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://github.com/openjdk/jdk21/blob/master/src/java.base/share/classes/java/lang/VirtualThread.java#L628&quot; target=&quot;_self&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #777777;&quot;&gt;https://github.com/openjdk/jdk21/blob/master/src/java.base/share/classes/java/lang/VirtualThread.java#L628&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;스레드를 언마운트/park하고 다시 마운트/unpark하는 것은 Future로 돌리는 것을 알 수 있다. 즉, 명시적인 KLT의 sleep/block을 수행하지 않는다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;Spring MVC Tomcat 하에서 테스트를 해보자. Virtual Thread를 사용하지 않는 Tomcat의 threads를 1로 설정하여 커널 스레드를 하나만 사용하게 하고, Virtual Thread에서도 커널 스레드를 하나만 사용하게 하여 처리량을 비교해 보겠다. 또한 호출은 100개의 요청을 동시에 보내 보겠다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;다음 컨트롤러를 호출한다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;background-color: #f9f9f9; color: #666666; text-align: start;&quot;&gt;&lt;code&gt;    @GetMapping(&quot;/test&quot;)
    @Operation(summary = &quot;테스트&quot;, description = &quot;테스트&quot;)
    public String test() throws Exception {
        Thread.sleep(1000l);
        log.info(&quot;{}&quot;, Thread.currentThread());
        return &quot;OK&quot;;
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;Tomcat은 다음 설정으로 스레드를 제한한다.&lt;/p&gt;
&lt;pre class=&quot;yaml&quot; style=&quot;background-color: #f9f9f9; color: #666666; text-align: start;&quot;&gt;&lt;code&gt;server:  
  tomcat:
    threads:
      max: 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;Virtual Thread는 가이드에 따라 다음 환경변수를 통해 스레드를 제한한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;169&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vZiBb/btsLe1E8QoQ/CKElii1DvMpvwT9cthTsQ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vZiBb/btsLe1E8QoQ/CKElii1DvMpvwT9cthTsQ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vZiBb/btsLe1E8QoQ/CKElii1DvMpvwT9cthTsQ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvZiBb%2FbtsLe1E8QoQ%2FCKElii1DvMpvwT9cthTsQ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;169&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;169&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #777777;&quot;&gt;출처:&lt;/span&gt;&lt;span style=&quot;color: #777777;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/Thread.html&quot; target=&quot;_self&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #777777;&quot;&gt;https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/Thread.html&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;Virtual Thread를 사용하지 않은 환경에서는100개의 호출이 동시에 발생했으나, Tomcat 스레드가 1이므로 호출 처리에 최대 1000밀리초 * 100의 처리 시간이 걸리고 1TPS의 처리량을 넘지 못한다. 즉, 동시성이 거의 없는 것을 볼 수 있다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;Name&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;# reqs&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;# fails&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;Avg&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;Min&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;Max&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;Median&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;req/s&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;failures/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;GET /test&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;23&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;0(0.00%)&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;11986&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;1021&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;22943&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;12000&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;0.99&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;0.00&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;Virtual Thread를 사용한 환경에서는 높은 TPS 처리량을 보인다. 100개의 호출이 동시에 발생했으나, non-blocking 방식으로 처리되어 최대 처리 시간 또한 1000l 정도다. 또한 로그에서 커널 스레드는 하나만 사용하는 것을 알 수 있다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;Name&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;# reqs&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;# fails&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;Avg&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;Min&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;Max&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;Median&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;req/s&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;failures/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;GET /test&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;928&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;0(0.00%)&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;1005&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;1001&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;1031&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;1001&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;89.19&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;0.00&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;pre class=&quot;angelscript&quot; style=&quot;background-color: #f9f9f9; color: #666666; text-align: start;&quot;&gt;&lt;code&gt;2024-02-05 13:17:26.329  INFO 70581 --- [               ] VirtualThread[#312]/runnable@ForkJoinPool-1-worker-1  
2024-02-05 13:17:26.336  INFO 70581 --- [               ] VirtualThread[#313]/runnable@ForkJoinPool-1-worker-1  
2024-02-05 13:17:26.339  INFO 70581 --- [               ] VirtualThread[#314]/runnable@ForkJoinPool-1-worker-1  
2024-02-05 13:17:26.349  INFO 70581 --- [               ] VirtualThread[#315]/runnable@ForkJoinPool-1-worker-1  
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;따라서 Tomcat, Spring MVC 하에서도 Netty/WebFlux와 처리 방식과 효율이 같으며, 네트워크 I/O처럼 CPU를 사용하지 않는 스레드 blocking 환경에서 사용하면 좋은 효율을 보여줄 수 있다고 판단할 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;참고&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://modimodi.tistory.com/54?utm_source=chatgpt.com&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://modimodi.tistory.com/54?utm_source=chatgpt.com&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1739519868094&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;JEP draft: Virtual Threads 가상스레드&quot; data-og-description=&quot;https://openjdk.java.net/jeps/8277131?fbclid=IwAR1A4nFUHY58UBPisTmhNucUP9ZIKKo1UvhSDdVh1Y63HWLWsih5vISghJE JEP draft: Virtual Threads (Preview) JEP draft: Virtual Threads (Preview) AuthorsRon Pressler, Alan BatemanOwnerAlan BatemanTypeFeatureScopeSEStatusD&quot; data-og-host=&quot;modimodi.tistory.com&quot; data-og-source-url=&quot;https://modimodi.tistory.com/54?utm_source=chatgpt.com&quot; data-og-url=&quot;https://modimodi.tistory.com/54&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/Jtxh4/hyYfSooZ7T/q2Yd7lYKwSjqLZxbSEXOr1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/fOi6p/hyYfZVmZ53/R2yZbmjdEyFHICEFfMsUj1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800&quot;&gt;&lt;a href=&quot;https://modimodi.tistory.com/54?utm_source=chatgpt.com&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://modimodi.tistory.com/54?utm_source=chatgpt.com&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/Jtxh4/hyYfSooZ7T/q2Yd7lYKwSjqLZxbSEXOr1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/fOi6p/hyYfZVmZ53/R2yZbmjdEyFHICEFfMsUj1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;JEP draft: Virtual Threads 가상스레드&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;https://openjdk.java.net/jeps/8277131?fbclid=IwAR1A4nFUHY58UBPisTmhNucUP9ZIKKo1UvhSDdVh1Y63HWLWsih5vISghJE JEP draft: Virtual Threads (Preview) JEP draft: Virtual Threads (Preview) AuthorsRon Pressler, Alan BatemanOwnerAlan BatemanTypeFeatureScopeSEStatusD&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;modimodi.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ㄴ 조금더 딥하게 정리되어 있는 내용&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://techblog.woowahan.com/15398/&quot;&gt;https://techblog.woowahan.com/15398/&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;Java의 미래, Virtual Thread | 우아한형제들 기술블로그&quot; data-ke-align=&quot;alignLeft&quot; data-og-description=&quot;JDK21에 공식 feature로 추가된 Virtual Thread에 대해 알아보고, Thread, Reactive Programming, Kotlin coroutines와 비교해봅니다.&quot; data-og-host=&quot;techblog.woowahan.com&quot; data-og-source-url=&quot;https://techblog.woowahan.com/15398/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bQj7Xj/hyXDaq7h6C/oVZ7k45vX3HoNKaazCygE1/img.png?width=4000&amp;amp;height=2088&amp;amp;face=0_0_4000_2088&quot; data-og-url=&quot;https://techblog.woowahan.com/15398/&quot;&gt;&lt;a href=&quot;https://techblog.woowahan.com/15398/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://techblog.woowahan.com/15398/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bQj7Xj/hyXDaq7h6C/oVZ7k45vX3HoNKaazCygE1/img.png?width=4000&amp;amp;height=2088&amp;amp;face=0_0_4000_2088');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Java의 미래, Virtual Thread | 우아한형제들 기술블로그&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;JDK21에 공식 feature로 추가된 Virtual Thread에 대해 알아보고, Thread, Reactive Programming, Kotlin coroutines와 비교해봅니다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;techblog.woowahan.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&lt;a href=&quot;https://d2.naver.com/helloworld/1203723&quot; target=&quot;_self&quot;&gt;&lt;span&gt;https://d2.naver.com/helloworld/1203723&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;a href=&quot;https://wikidocs.net/232300&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;https://wikidocs.net/232300&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;10-6 인터럽트 기반 I/O&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;[TOC] 인터럽트에 의한 I/O 방식은 운영체제가 하드웨어 I/O 장치와 상호작용하는 효율적인 메커니즘 중 하나입니다. 이 방식은 운영체제가 I/O 작업을 수행하면서 CPU의&amp;hellip;&quot; data-og-host=&quot;wikidocs.net&quot; data-og-source-url=&quot;https://wikidocs.net/232300&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/gpMse/hyXGEYoYsE/mqYFpmDwGTo0TcNyCfmfuK/img.png?width=277&amp;amp;height=360&amp;amp;face=0_0_277_360&quot; data-og-url=&quot;https://wikidocs.net/232300&quot;&gt;&lt;a href=&quot;https://wikidocs.net/232300&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://wikidocs.net/232300&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/gpMse/hyXGEYoYsE/mqYFpmDwGTo0TcNyCfmfuK/img.png?width=277&amp;amp;height=360&amp;amp;face=0_0_277_360');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;10-6 인터럽트 기반 I/O&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;[TOC] 인터럽트에 의한 I/O 방식은 운영체제가 하드웨어 I/O 장치와 상호작용하는 효율적인 메커니즘 중 하나입니다. 이 방식은 운영체제가 I/O 작업을 수행하면서 CPU의&amp;hellip;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;wikidocs.net&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;a href=&quot;https://f-lab.kr/insight/understanding-non-blocking-io-and-threads&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;https://f-lab.kr/insight/understanding-non-blocking-io-and-threads&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;논 블로킹 I/O와 스레드의 이해&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;논 블로킹 I/O와 스레드의 관계를 이해하고, 논 블로킹 I/O의 실제 적용 사례 및 도전과제와 해결 방안을 탐구합니다.&quot; data-og-host=&quot;f-lab.kr&quot; data-og-source-url=&quot;https://f-lab.kr/ai-blog/understanding-non-blocking-io-and-threads&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/GiB79/hyXDadATiH/UgAhGP2KKJcRefLPVISpm0/img.jpg?width=1792&amp;amp;height=1024&amp;amp;face=0_0_1792_1024&quot; data-og-url=&quot;https://f-lab.kr/ai-blog/understanding-non-blocking-io-and-threads&quot;&gt;&lt;a href=&quot;https://f-lab.kr/ai-blog/understanding-non-blocking-io-and-threads&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://f-lab.kr/ai-blog/understanding-non-blocking-io-and-threads&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/GiB79/hyXDadATiH/UgAhGP2KKJcRefLPVISpm0/img.jpg?width=1792&amp;amp;height=1024&amp;amp;face=0_0_1792_1024');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;논 블로킹 I/O와 스레드의 이해&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;논 블로킹 I/O와 스레드의 관계를 이해하고, 논 블로킹 I/O의 실제 적용 사례 및 도전과제와 해결 방안을 탐구합니다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;f-lab.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&lt;a href=&quot;https://letsmakemyselfprogrammer.tistory.com/98&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;https://letsmakemyselfprogrammer.tistory.com/98&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;JavaThread 에 대해 깊게 이해해보자 (feat. Openjdk 커널 분석)&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;Thread에 대한 기초적인 os 지식은 이 글(쓰레드(Thread)와 동기화 문제)을 참고하기 바람 Thread는 user가 관리하느냐, os가 관리하느냐에 따라 User-Level-Thread 또는 Kernel-Level-Thread 로 나뉜다. 두 가지의 &quot; data-og-host=&quot;letsmakemyselfprogrammer.tistory.com&quot; data-og-source-url=&quot;https://letsmakemyselfprogrammer.tistory.com/98&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/eJY8U/hyXDhjsY6C/7aLZpJthhZQvEsvmaDVcI0/img.gif?width=271&amp;amp;height=205&amp;amp;face=0_0_271_205,https://scrap.kakaocdn.net/dn/bCJuzd/hyXGN16GEy/ObcuDaB81bPsOQoyj063LK/img.gif?width=271&amp;amp;height=205&amp;amp;face=0_0_271_205,https://scrap.kakaocdn.net/dn/rgOE4/hyXGE5aGGw/wJrkBMa2q5N3iC0bK29UPK/img.png?width=639&amp;amp;height=321&amp;amp;face=0_0_639_321&quot; data-og-url=&quot;https://letsmakemyselfprogrammer.tistory.com/98&quot;&gt;&lt;a href=&quot;https://letsmakemyselfprogrammer.tistory.com/98&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://letsmakemyselfprogrammer.tistory.com/98&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/eJY8U/hyXDhjsY6C/7aLZpJthhZQvEsvmaDVcI0/img.gif?width=271&amp;amp;height=205&amp;amp;face=0_0_271_205,https://scrap.kakaocdn.net/dn/bCJuzd/hyXGN16GEy/ObcuDaB81bPsOQoyj063LK/img.gif?width=271&amp;amp;height=205&amp;amp;face=0_0_271_205,https://scrap.kakaocdn.net/dn/rgOE4/hyXGE5aGGw/wJrkBMa2q5N3iC0bK29UPK/img.png?width=639&amp;amp;height=321&amp;amp;face=0_0_639_321');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;JavaThread 에 대해 깊게 이해해보자 (feat. Openjdk 커널 분석)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Thread에 대한 기초적인 os 지식은 이 글(쓰레드(Thread)와 동기화 문제)을 참고하기 바람 Thread는 user가 관리하느냐, os가 관리하느냐에 따라 User-Level-Thread 또는 Kernel-Level-Thread 로 나뉜다. 두 가지의&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;letsmakemyselfprogrammer.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&lt;a href=&quot;https://konghana01.tistory.com/649&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;https://konghana01.tistory.com/649&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;Java Thread, JDK 뒤져보기&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;오늘 아침 지하철을 타고 오면서 문득 이런 궁금증이 떠올랐습니다. 'cpu 사양에 따라 가용한 쓰레드의 개수는 한정적일텐데, 자바에서는 어떻게 쓰레드의 개수를 마구마구 늘릴 수 있는거지?' &quot; data-og-host=&quot;konghana01.tistory.com&quot; data-og-source-url=&quot;https://konghana01.tistory.com/649&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/co0GZw/hyXDjInNV5/HjmTDwS8qzROaJJHfCnKy1/img.gif?width=370&amp;amp;height=240&amp;amp;face=0_0_370_240,https://scrap.kakaocdn.net/dn/SUHtu/hyXDcJdYRc/GCbwvxRD3FgeGaAhpcXmA1/img.gif?width=370&amp;amp;height=240&amp;amp;face=0_0_370_240,https://scrap.kakaocdn.net/dn/bx2GPt/hyXGyRp2xN/2gkGznfvrHpDyKykqkNqrK/img.png?width=1147&amp;amp;height=630&amp;amp;face=0_0_1147_630&quot; data-og-url=&quot;https://konghana01.tistory.com/649&quot;&gt;&lt;a href=&quot;https://konghana01.tistory.com/649&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://konghana01.tistory.com/649&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/co0GZw/hyXDjInNV5/HjmTDwS8qzROaJJHfCnKy1/img.gif?width=370&amp;amp;height=240&amp;amp;face=0_0_370_240,https://scrap.kakaocdn.net/dn/SUHtu/hyXDcJdYRc/GCbwvxRD3FgeGaAhpcXmA1/img.gif?width=370&amp;amp;height=240&amp;amp;face=0_0_370_240,https://scrap.kakaocdn.net/dn/bx2GPt/hyXGyRp2xN/2gkGznfvrHpDyKykqkNqrK/img.png?width=1147&amp;amp;height=630&amp;amp;face=0_0_1147_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Java Thread, JDK 뒤져보기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;오늘 아침 지하철을 타고 오면서 문득 이런 궁금증이 떠올랐습니다. 'cpu 사양에 따라 가용한 쓰레드의 개수는 한정적일텐데, 자바에서는 어떻게 쓰레드의 개수를 마구마구 늘릴 수 있는거지?'&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;konghana01.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&lt;a href=&quot;https://blog.naver.com/PostView.naver?blogId=thisryan97&amp;amp;logNo=223457538567&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;https://blog.naver.com/PostView.naver?blogId=thisryan97&amp;amp;logNo=223457538567&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;Blocking I/O와 Non-blocking I/O&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;  I/O I/O란, Input과 Output의 줄임말이다. I/O의 종류에는 네트워크(소켓), 파일, 파이프, 디...&quot; data-og-host=&quot;blog.naver.com&quot; data-og-source-url=&quot;https://blog.naver.com/PostView.naver?blogId=thisryan97&amp;amp;logNo=223457538567&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/b2fHJ2/hyXGCM3cjG/XKRUIpjnBuv1O7l0FX13a1/img.png?width=743&amp;amp;height=362&amp;amp;face=0_0_743_362&quot; data-og-url=&quot;https://blog.naver.com/thisryan97/223457538567&quot;&gt;&lt;a href=&quot;https://blog.naver.com/thisryan97/223457538567&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://blog.naver.com/PostView.naver?blogId=thisryan97&amp;amp;logNo=223457538567&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/b2fHJ2/hyXGCM3cjG/XKRUIpjnBuv1O7l0FX13a1/img.png?width=743&amp;amp;height=362&amp;amp;face=0_0_743_362');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Blocking I/O와 Non-blocking I/O&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;  I/O I/O란, Input과 Output의 줄임말이다. I/O의 종류에는 네트워크(소켓), 파일, 파이프, 디...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;blog.naver.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;</description>
      <category>프로그래밍언어/Java&amp;amp;Servlet</category>
      <category>CPU</category>
      <category>epoll</category>
      <category>java</category>
      <category>OS</category>
      <category>thread</category>
      <category>virtual thread</category>
      <category>가상쓰레드</category>
      <author>여성게</author>
      <guid isPermaLink="true">https://coding-start.tistory.com/443</guid>
      <comments>https://coding-start.tistory.com/443#entry443comment</comments>
      <pubDate>Sat, 30 Nov 2024 18:37:41 +0900</pubDate>
    </item>
    <item>
      <title>Spring - @Transaction propagation&amp;amp;중첩 트랜잭션 롤백</title>
      <link>https://coding-start.tistory.com/442</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;참고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://techblog.woowahan.com/2606/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://techblog.woowahan.com/2606/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1732865478826&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;응? 이게 왜 롤백되는거지? | 우아한형제들 기술블로그&quot; data-og-description=&quot;이 글은 얼마 전 에러로그 하나에 대한 호기심과 의문으로 시작해서 스프링의 트랜잭션 내에서 예외가 어떻게 처리되는지를 이해하기 위해 삽질을 해본 경험을 토대로 쓰여졌습니다. 스프링의 &quot; data-og-host=&quot;techblog.woowahan.com&quot; data-og-source-url=&quot;https://techblog.woowahan.com/2606/&quot; data-og-url=&quot;https://techblog.woowahan.com/2606/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/iF8V8/hyXGJZxP5t/0kQIuw1JqrMwSo8J02nKO1/img.jpg?width=1640&amp;amp;height=856&amp;amp;face=0_0_1640_856,https://scrap.kakaocdn.net/dn/lmpWI/hyXGFJCmDm/YnAY3OmCb4MOm8HcjMWtA1/img.jpg?width=1640&amp;amp;height=856&amp;amp;face=0_0_1640_856,https://scrap.kakaocdn.net/dn/cBTMmR/hyXGJL0MxJ/kxqelgzIeFsIUovopDLSB0/img.png?width=1200&amp;amp;height=290&amp;amp;face=0_0_1200_290&quot;&gt;&lt;a href=&quot;https://techblog.woowahan.com/2606/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://techblog.woowahan.com/2606/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/iF8V8/hyXGJZxP5t/0kQIuw1JqrMwSo8J02nKO1/img.jpg?width=1640&amp;amp;height=856&amp;amp;face=0_0_1640_856,https://scrap.kakaocdn.net/dn/lmpWI/hyXGFJCmDm/YnAY3OmCb4MOm8HcjMWtA1/img.jpg?width=1640&amp;amp;height=856&amp;amp;face=0_0_1640_856,https://scrap.kakaocdn.net/dn/cBTMmR/hyXGJL0MxJ/kxqelgzIeFsIUovopDLSB0/img.png?width=1200&amp;amp;height=290&amp;amp;face=0_0_1200_290');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;응? 이게 왜 롤백되는거지? | 우아한형제들 기술블로그&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;이 글은 얼마 전 에러로그 하나에 대한 호기심과 의문으로 시작해서 스프링의 트랜잭션 내에서 예외가 어떻게 처리되는지를 이해하기 위해 삽질을 해본 경험을 토대로 쓰여졌습니다. 스프링의&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;techblog.woowahan.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <author>여성게</author>
      <guid isPermaLink="true">https://coding-start.tistory.com/442</guid>
      <comments>https://coding-start.tistory.com/442#entry442comment</comments>
      <pubDate>Fri, 29 Nov 2024 16:31:22 +0900</pubDate>
    </item>
    <item>
      <title>Redis - Redisson을 이용한 분산락(Distributed lock)</title>
      <link>https://coding-start.tistory.com/441</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;참고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://helloworld.kurly.com/blog/distributed-redisson-lock/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://helloworld.kurly.com/blog/distributed-redisson-lock/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1732864400535&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;풀필먼트 입고 서비스팀에서 분산락을 사용하는 방법 - Spring Redisson&quot; data-og-description=&quot;어노테이션 기반으로 분산락을 사용하는 방법에 대해 소개합니다.&quot; data-og-host=&quot;helloworld.kurly.com&quot; data-og-source-url=&quot;https://helloworld.kurly.com/blog/distributed-redisson-lock/&quot; data-og-url=&quot;http://thefarmersfront.github.io/blog/distributed-redisson-lock/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cOZlQ0/hyXDjg9Nm2/wgKa15hokfpxiBCk9TgHz0/img.png?width=1452&amp;amp;height=1452&amp;amp;face=0_0_1452_1452,https://scrap.kakaocdn.net/dn/bCrkAc/hyXDfy3oyi/QOhCBziXzSHp36ffubVaD0/img.png?width=1670&amp;amp;height=1610&amp;amp;face=0_0_1670_1610,https://scrap.kakaocdn.net/dn/b9OSLJ/hyXGOT41Al/H9n5YPTVzBXJueWHTtC0RK/img.png?width=1656&amp;amp;height=1550&amp;amp;face=0_0_1656_1550&quot;&gt;&lt;a href=&quot;https://helloworld.kurly.com/blog/distributed-redisson-lock/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://helloworld.kurly.com/blog/distributed-redisson-lock/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cOZlQ0/hyXDjg9Nm2/wgKa15hokfpxiBCk9TgHz0/img.png?width=1452&amp;amp;height=1452&amp;amp;face=0_0_1452_1452,https://scrap.kakaocdn.net/dn/bCrkAc/hyXDfy3oyi/QOhCBziXzSHp36ffubVaD0/img.png?width=1670&amp;amp;height=1610&amp;amp;face=0_0_1670_1610,https://scrap.kakaocdn.net/dn/b9OSLJ/hyXGOT41Al/H9n5YPTVzBXJueWHTtC0RK/img.png?width=1656&amp;amp;height=1550&amp;amp;face=0_0_1656_1550');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;풀필먼트 입고 서비스팀에서 분산락을 사용하는 방법 - Spring Redisson&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;어노테이션 기반으로 분산락을 사용하는 방법에 대해 소개합니다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;helloworld.kurly.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>distributed lock</category>
      <category>Lock</category>
      <category>Redis</category>
      <category>Redisson</category>
      <category>락</category>
      <category>분산락</category>
      <author>여성게</author>
      <guid isPermaLink="true">https://coding-start.tistory.com/441</guid>
      <comments>https://coding-start.tistory.com/441#entry441comment</comments>
      <pubDate>Fri, 29 Nov 2024 16:13:33 +0900</pubDate>
    </item>
    <item>
      <title>JPA - @JoinColumn 정리</title>
      <link>https://coding-start.tistory.com/439</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;참고블로그&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://ksh-coding.tistory.com/105&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://ksh-coding.tistory.com/105&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1721302747790&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[JPA] @JoinColumn 파헤치기 (feat. JPA 연관관계별 사용)&quot; data-og-description=&quot;✌  0. 들어가기 전 JPA 강의 중에 @JoinColumn을 마스터하면 JPA 연관관계를 어느정도는 다 알 수 있다고 들었다. 그래서, 여러 JPA 개념 중에 @JoinColumn에 대해 파헤쳐보면서 JPA 연관관계를 이해해보&quot; data-og-host=&quot;ksh-coding.tistory.com&quot; data-og-source-url=&quot;https://ksh-coding.tistory.com/105&quot; data-og-url=&quot;https://ksh-coding.tistory.com/105&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bh3qbE/hyWzysXhgW/5Tyku282D3jrjhsZ3uMGk0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bI2xQ2/hyWzBJYLZ6/M2SntdaEfizqo4iFRrjwxK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/b36pQ7/hyWCCmYW7q/boHrclu2ZUZcdridQwyuSk/img.png?width=1346&amp;amp;height=580&amp;amp;face=0_0_1346_580&quot;&gt;&lt;a href=&quot;https://ksh-coding.tistory.com/105&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://ksh-coding.tistory.com/105&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bh3qbE/hyWzysXhgW/5Tyku282D3jrjhsZ3uMGk0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bI2xQ2/hyWzBJYLZ6/M2SntdaEfizqo4iFRrjwxK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/b36pQ7/hyWCCmYW7q/boHrclu2ZUZcdridQwyuSk/img.png?width=1346&amp;amp;height=580&amp;amp;face=0_0_1346_580');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[JPA] @JoinColumn 파헤치기 (feat. JPA 연관관계별 사용)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;✌  0. 들어가기 전 JPA 강의 중에 @JoinColumn을 마스터하면 JPA 연관관계를 어느정도는 다 알 수 있다고 들었다. 그래서, 여러 JPA 개념 중에 @JoinColumn에 대해 파헤쳐보면서 JPA 연관관계를 이해해보&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;ksh-coding.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;@JoinColumn의 name 속성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;name 속성은 연관관계의 주인(@JoinColumn을 가진)의 테이블의 FK 필드명을 뜻한다.&lt;/p&gt;
&lt;pre id=&quot;code_1721303461925&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;* 참조하는 Entity가 존재할 경우
: 참조하는 Entity의 필드명 + &quot;_&quot; + 참조된 기본 키 열의 이름

public class Station {
	...
	
    @ManyToOne
    @JoinColumn
    private Line line;	
}

: 참조하는 Entity(Station)의 필드명(line) + &quot;_&quot; + 참조된 Entity(Line)의 기본 키 열의 이름(id)
: line + &quot;_&quot; + id = &quot;line_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;위 클래스를 기반으로 설명을 하자면, Station은 Line과 연관 관계가 있는데, Station 데이터베이스 테이블에 Line과의 연관관계인 FK가 LINE_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;i&gt;&lt;b&gt;기본값 : 위 클래스 기준 Line 필드명(line) + Line클래스의 PK 필드명&lt;/b&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;@JoinColumn의 referencedColumnName 속성&lt;/h3&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;referencedColumnName속성은 연관관계의 주인(@JoinColumn을 가진)과 연관관계에 있는 테이블의 PK 필드명(연관 관계 주인의 FK와 연결되는 필드)을 뜻한다.&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;i&gt;&lt;b&gt;기본값 : 위 클래스 기준 Line클래스의 PK 필드명&lt;/b&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Web/JPA</category>
      <author>여성게</author>
      <guid isPermaLink="true">https://coding-start.tistory.com/439</guid>
      <comments>https://coding-start.tistory.com/439#entry439comment</comments>
      <pubDate>Thu, 18 Jul 2024 21:03:15 +0900</pubDate>
    </item>
    <item>
      <title>네트워크 - VPN과 NAT란?</title>
      <link>https://coding-start.tistory.com/438</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-06-24 오후 12.27.18.png&quot; data-origin-width=&quot;1582&quot; data-origin-height=&quot;854&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oib6S/btsH8CKkgNY/5lKVWbaTwLPAkJgcSVEVqK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oib6S/btsH8CKkgNY/5lKVWbaTwLPAkJgcSVEVqK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oib6S/btsH8CKkgNY/5lKVWbaTwLPAkJgcSVEVqK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Foib6S%2FbtsH8CKkgNY%2F5lKVWbaTwLPAkJgcSVEVqK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1582&quot; height=&quot;854&quot; data-filename=&quot;스크린샷 2024-06-24 오후 12.27.18.png&quot; data-origin-width=&quot;1582&quot; data-origin-height=&quot;854&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;VPN이란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;VPN이란 Virtual Private Network의 약자로 인터넷과 같은 공중망을 마치 전용회선처럼 사용해 보안성을 향상하면서 사설망을 이용하지 않기 때문에 비용문제까지 해결한 네트워크다. 실제로 아주 먼 거리의 지사끼리 전용회선으로 통신하기 위해서는 큰 비용이 발생한다. 이때 VPN을 사용하면 비용 절감은 물론 지사간의 중요 데이터를 송수신할 경우에 보안도 지킬 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공중망을 통해 데이터가 송수신되더라도 정보 유출이 없도록 라우터 체계를 비공개하고, 데이터를 암호화하고 사용자 인증 기능을 추가하는 등의 다양한 보안기능을 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;VPN 터널링&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;터널링이란 연결해야할 두 지점간에 마치 터널이 뚫린 것처럼 통신 통로를 생성하는 것을 말한다. 이 터널은 터널링을 지원하는 프로토콜을 사용하여 구현되고 있으며 사설망과 같은 보안 기능을 제공한다. 경유지를 통하지 않고 두 지점을 바로 연결시켰기 때문에 tracert 명령을 사용해보면 라우팅 경유지 정보가 출력되지 않는다. 또한 터널링되는 데이터는 터널링 구간에서 캡슐화 된다.&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;VPN 터널링 프로토콜&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-06-24 오후 12.32.28.png&quot; data-origin-width=&quot;1580&quot; data-origin-height=&quot;1004&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cC9BQx/btsH82oaVRZ/DKUowSwOOUKEWFheKy1Bh1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cC9BQx/btsH82oaVRZ/DKUowSwOOUKEWFheKy1Bh1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cC9BQx/btsH82oaVRZ/DKUowSwOOUKEWFheKy1Bh1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcC9BQx%2FbtsH82oaVRZ%2FDKUowSwOOUKEWFheKy1Bh1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1580&quot; height=&quot;1004&quot; data-filename=&quot;스크린샷 2024-06-24 오후 12.32.28.png&quot; data-origin-width=&quot;1580&quot; data-origin-height=&quot;1004&quot;/&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;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;계층&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;프로토콜&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot; rowspan=&quot;3&quot;&gt;2계층&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;PPTP&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;- MS 사에서 개발한 프로토콜로 페이로드를 암호화하고 IP헤더로 캡슐화하여 전송한다.&lt;br /&gt;- VPN에 비밀번호를 사용해 로그인하게 되는데 보안성이 미약해 요즘엔 거의 사용하지 않는 프로토콜이다.&lt;br /&gt;- 하나의 터널에 하나의 연결만 허용한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;L2F&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;- 시스코 사에서 개발한 프로토콜로 TCP가 아닌 UDP를 사용한다는 특징이 있다.&lt;br /&gt;- PPTP와는 다르게 하나의 터널에 여러 연결을 허용한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;L2TP&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;- PPTP와 L2F를 결합한 방법으로 PPTP와 캡슐화 방식은 동일하지만 추가적으로 IPSec의 ESP를 도입해 보안기능을 제공한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot; rowspan=&quot;2&quot;&gt;3계층&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;IPSec&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;- IPSec은 네트워크 계층의 보안을 위해 IETF에 의해 제안되었으며 VPN 구현에 널리 쓰이고 있다.&lt;br /&gt;- AH(Authentication Header)와 ESP(Encapsulation Security)를 통해 데이터의 인증, 무결성, 기밀성을 제공하며, 전송모드와 터널모드 2가지가 존재한다.&lt;br /&gt;- IPSec을 사용하면 IP 헤더와 페이로드로 이루어져 있는 IP패킷이 터널 안으로 들어가면서 AH가 추가된 후 캡슐화가 이루어지고 ESP 헤더가 삽입되면서 IP패킷이 암호화되게 된다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;MPLS&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;- MPLS는 시스코 사에서 정한 표준 중 하나로 이 MPLS 통신 네트워크를 이용해 VPN을 제공하는 것이 MPS VPN이다.&lt;br /&gt;- 여타 VPN 구조에 비해 도입 및 운영이 간단하고 비용이 저렴하다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;4~7계층&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;SSL&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;- 넷스케이프 사에서 만든 프로토콜로 웹서버와 웹브라우저 간의 안전한 통신을 목적으로 한다.&lt;br /&gt;- IPSec과 기능은 거의 동일하나 암호화 방식이나 하드웨어가 불필요한 점 등 차이가 있다.&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;h4 id=&quot;id-20220914신용보증기금공중망+VPNFU-ESP(EncapsulatingSecurityPayload)&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;ESP(Encapsulating Security Payload)&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #444444; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;두 개의 시스템이 송수신하는 IP패킷에 대한 인증과 암호화를 실시한다. 데이터의 무결성과 기밀성을 보장한다.&lt;/li&gt;
&lt;li&gt;RFC 2406에 정의되어 있으며, IPv4 프로토콜 필드에서 50번을 사용한다.&lt;/li&gt;
&lt;li&gt;인증 알고리즘(MD5-HMAC, SHA-HMAC)과 대칭키 암호화 알고리즘(DES, 3DES, AES)를 지원한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;id-20220914신용보증기금공중망+VPNFU-ESPMode&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;ESP Mode&lt;/h4&gt;
&lt;p style=&quot;background-color: #ffffff; color: #444444; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;ESP Mode에 따라 encapsulation 범위가 달라진다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #444444; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;ESP Transport Mode&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 상위 계층의 Payload(헤더가 포함된 전체 데이터)만 보호한다. 즉, IP헤더를 제외한 IP패킷의 Payload만 보호한다. AH Header만 붙는다.&lt;/li&gt;
&lt;li&gt;&lt;span&gt;ESP Tunnel Mode&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: AH Tunnel과 달리 IP 패킷 전체를 보호한다. AH Header와 New IP Header가 붙는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;id-20220914신용보증기금공중망+VPNFU-VPN-보안기능&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;VPN - 보안 기능&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #444444; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터 기밀성 : 송수신되는 데이터의 기밀을 지키기 위해 암호화하여 전송한다.&lt;/li&gt;
&lt;li&gt;데이터 무결성 : 송수신되는 데이터의 내용이 중간에 변경되지 않았음을 보장하기 위해 암호화 및 전자서명을 사용한다.&lt;/li&gt;
&lt;li&gt;데이터 인증 : 수신한 데이터가 알맞은 송신자에 의해 전송됐음을 보장한다.&lt;/li&gt;
&lt;li&gt;접근통제 : 인증된 사용자만 접근을 허용한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;id-20220914신용보증기금공중망+VPNFU-NAT(NetworkAddressTranslation)&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;NAT(Network Address Translation)란?&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #444444; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;NAT는 사설 IP를 공인 IP로 변경할 때 필요한 주소 변환 서비스이다. 좀더 자세히 설명하자면, 라우터 등의 장비를 사용하여 다수의 사설 IP(Private IP)를 하나의 공인 IP(Public IP) 주소로 변환하는 기술이다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #444444; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;주로 기업이나 기관에서는 내부망을 사용하는 PC에 사설 IP를 제공하고 외부 인터넷에 연결 시엔 공인 IP 하나를 같이 사용하는 형태로 운영된다. NAT는 다수의 주소 변환 정보에 대해 IP주소와 Port 번호로 구성된 NAT Forwarding Table을 보관하고 있고 이에 맞게 주소 변환 서비스를 제공한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #444444; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #444444; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;아래 그림에서 NAT Table의 특징은 아래와 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #444444; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;내부 네트워크에 위치한 호스트들의 사설 IP와 Port 번호에 대한 정보를 가지고 있음.&lt;/li&gt;
&lt;li&gt;외부로 나갈 때의 동일한 공인 IP와 각기 다른 포트 번호를 가지고 있음.&lt;/li&gt;
&lt;li&gt;목적지 주소의 공인 IP와 서비스에 사용된 동일한 서비스 포트 번호를 가지고 있음.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #444444; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #444444; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;공인 IP는 같으나, 포트 번호를 다르게 할당하여 각각의 호스트를 구분 짓는 구조이다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #444444; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-06-24 오후 12.43.25.png&quot; data-origin-width=&quot;1612&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFDryw/btsH8AMwOpW/Vau07Jlq3um2lKEsDSPWWK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFDryw/btsH8AMwOpW/Vau07Jlq3um2lKEsDSPWWK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFDryw/btsH8AMwOpW/Vau07Jlq3um2lKEsDSPWWK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFDryw%2FbtsH8AMwOpW%2FVau07Jlq3um2lKEsDSPWWK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1612&quot; height=&quot;1080&quot; data-filename=&quot;스크린샷 2024-06-24 오후 12.43.25.png&quot; data-origin-width=&quot;1612&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 id=&quot;id-20220914신용보증기금공중망+VPNFU-NAT가필요한이유?&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;NAT가 필요한 이유?&lt;/h4&gt;
&lt;p style=&quot;background-color: #ffffff; color: #444444; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;크게 두가지 목적을 가진다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: #ffffff; color: #444444; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;공인 IP 주소를 절약
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;인터넷 상의 공인 IP 주소의 개수는 한정되어 있어 NAT를 이용해 공인 IP 사용을 줄인다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;보안
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;NAT가 마치 방화벽 역할을 하여 보안을 높힐 수 있다.(NAT는 물론 방화벽 형태일 수 있지만, 일반 라우터 형태일 수 있다.)&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&quot;id-20220914신용보증기금공중망+VPNFU-NAT의동작원리-IPmasquerading&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;NAT의 동작 원리 - IP masquerading&lt;/h4&gt;
&lt;p style=&quot;background-color: #ffffff; color: #444444; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;NAT는 하나의 공인 IP 뒤로 여러 개의 사설 IP 공간을 은닉하는 IP masquerading(마스쿼레이딩) 기법을 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고: &lt;a href=&quot;https://liveyourit.tistory.com/3&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://liveyourit.tistory.com/3&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1719199985588&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[네트워크] VPN이란 ? 정의/ 터널링/터널링 프로토콜(IPSecVPN, MPLSVPN, SSL 등)&quot; data-og-description=&quot;VPN(Virtual Private Network)는 의미 그대로 가상 사설망을 의미한다. VPN 이라는 말은 많이 들어봤는데... 가상 사설망이 정확히 뭔지? 왜 쓰는지? 관련 터널링 프로토콜에는 무엇이 있는지? 등을 이론적&quot; data-og-host=&quot;liveyourit.tistory.com&quot; data-og-source-url=&quot;https://liveyourit.tistory.com/3&quot; data-og-url=&quot;https://liveyourit.tistory.com/3&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/fcU04/hyWoDgJglm/1KJkhJIt8ws4HtcKQaNGdK/img.png?width=800&amp;amp;height=417&amp;amp;face=0_0_800_417,https://scrap.kakaocdn.net/dn/be9luL/hyWrQrXP5x/n3RKgFUrsKvCNm2NXxsx41/img.png?width=800&amp;amp;height=417&amp;amp;face=0_0_800_417,https://scrap.kakaocdn.net/dn/wSnCw/hyWrXLoGxQ/WVdMikwB75OXJd6CQUwzh1/img.png?width=799&amp;amp;height=797&amp;amp;face=0_0_799_797&quot;&gt;&lt;a href=&quot;https://liveyourit.tistory.com/3&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://liveyourit.tistory.com/3&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/fcU04/hyWoDgJglm/1KJkhJIt8ws4HtcKQaNGdK/img.png?width=800&amp;amp;height=417&amp;amp;face=0_0_800_417,https://scrap.kakaocdn.net/dn/be9luL/hyWrQrXP5x/n3RKgFUrsKvCNm2NXxsx41/img.png?width=800&amp;amp;height=417&amp;amp;face=0_0_800_417,https://scrap.kakaocdn.net/dn/wSnCw/hyWrXLoGxQ/WVdMikwB75OXJd6CQUwzh1/img.png?width=799&amp;amp;height=797&amp;amp;face=0_0_799_797');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[네트워크] VPN이란 ? 정의/ 터널링/터널링 프로토콜(IPSecVPN, MPLSVPN, SSL 등)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;VPN(Virtual Private Network)는 의미 그대로 가상 사설망을 의미한다. VPN 이라는 말은 많이 들어봤는데... 가상 사설망이 정확히 뭔지? 왜 쓰는지? 관련 터널링 프로토콜에는 무엇이 있는지? 등을 이론적&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;liveyourit.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>인프라/네트워크(기초)</category>
      <category>ipsec</category>
      <category>NAT</category>
      <category>vpn</category>
      <category>네트워크</category>
      <category>보안</category>
      <category>터널링</category>
      <category>터널링 프로토콜</category>
      <author>여성게</author>
      <guid isPermaLink="true">https://coding-start.tistory.com/438</guid>
      <comments>https://coding-start.tistory.com/438#entry438comment</comments>
      <pubDate>Mon, 24 Jun 2024 12:45:25 +0900</pubDate>
    </item>
    <item>
      <title>Kafka - 신뢰성 있는 카프카 애플리케이션을 만드는 3가지 방법(멱등성 프로듀서, 트랜잭션 컨슈머, 컨슈머 중복 적재)</title>
      <link>https://coding-start.tistory.com/437</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 멱등성 프로듀서&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1.1. 중복 데이터 produce 문제 상황&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 네트워크에 이슈가 생겨서, Acknowledge 전송이 실패되면 중복으로 데이터를 produce할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1.2. 멱등성 프로듀서 사용&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PID(Producer unique ID)를 같이 보내서 브로커에 메시지가 저장되어있음을 같이 저장해서 중복 메시지가 발행되면 토픽에 개시하지 않음.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-06-18 오후 4.49.31.png&quot; data-origin-width=&quot;1420&quot; data-origin-height=&quot;690&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bBHoCy/btsH3nyYq25/yvqJbFtBJ4ytmEFsqkDBz1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bBHoCy/btsH3nyYq25/yvqJbFtBJ4ytmEFsqkDBz1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bBHoCy/btsH3nyYq25/yvqJbFtBJ4ytmEFsqkDBz1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbBHoCy%2FbtsH3nyYq25%2FyvqJbFtBJ4ytmEFsqkDBz1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1420&quot; height=&quot;690&quot; data-filename=&quot;스크린샷 2024-06-18 오후 4.49.31.png&quot; data-origin-width=&quot;1420&quot; data-origin-height=&quot;690&quot;/&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;아래는 실제 3.0 이상의 카프카 프로듀서 팩토리 코드인데, 디폴트로 true로 설정된 코드.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-06-18 오후 4.49.46.png&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;676&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cgYW2O/btsH3rVuUsQ/c7IA3KKO57kR4O7xC5BCI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cgYW2O/btsH3rVuUsQ/c7IA3KKO57kR4O7xC5BCI1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cgYW2O/btsH3rVuUsQ/c7IA3KKO57kR4O7xC5BCI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcgYW2O%2FbtsH3rVuUsQ%2Fc7IA3KKO57kR4O7xC5BCI1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1448&quot; height=&quot;676&quot; data-filename=&quot;스크린샷 2024-06-18 오후 4.49.46.png&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;676&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-06-18 오후 4.49.59.png&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;796&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cTCcce/btsH2oSVfPo/46TYPikFowKlI3hUiubFq0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cTCcce/btsH2oSVfPo/46TYPikFowKlI3hUiubFq0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cTCcce/btsH2oSVfPo/46TYPikFowKlI3hUiubFq0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcTCcce%2FbtsH2oSVfPo%2F46TYPikFowKlI3hUiubFq0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1448&quot; height=&quot;796&quot; data-filename=&quot;스크린샷 2024-06-18 오후 4.49.59.png&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;796&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 트랜잭션 컨슈머 + 프로듀서&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨슈머와 프로듀서를 하나의 트랜잭션으로 묶어서 처리하는 방식임. 특이하게 커밋을 Producer가 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-06-18 오후 4.50.22.png&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;698&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Dxj0U/btsH4BprN4w/BV80NPYuOF9moE6mtPKJj0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Dxj0U/btsH4BprN4w/BV80NPYuOF9moE6mtPKJj0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Dxj0U/btsH4BprN4w/BV80NPYuOF9moE6mtPKJj0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDxj0U%2FbtsH4BprN4w%2FBV80NPYuOF9moE6mtPKJj0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1448&quot; height=&quot;698&quot; data-filename=&quot;스크린샷 2024-06-18 오후 4.50.22.png&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;698&quot;/&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;트랜잭션 컨슈머 + 프로듀서 구성으로 되어있고 그 프로듀서가 발행한 이벤트를 consume하는 컨슈머는 반드시isolation.level을 read_committed로 설정해주어야한다.(트랜잭션 commit이 성공한 레코드만 컨슘하는 메커니즘)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-06-18 오후 4.50.56.png&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;698&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GnQGw/btsH2HYXFKM/x5cQ1T4yU9akEy2fuifNC0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GnQGw/btsH2HYXFKM/x5cQ1T4yU9akEy2fuifNC0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GnQGw/btsH2HYXFKM/x5cQ1T4yU9akEy2fuifNC0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGnQGw%2FbtsH2HYXFKM%2Fx5cQ1T4yU9akEy2fuifNC0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1448&quot; height=&quot;698&quot; data-filename=&quot;스크린샷 2024-06-18 오후 4.50.56.png&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;698&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 컨슈머의 중복 적재 방지&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유니크 키 값을 record key나 혹은 메시지가 발행된 타임스탬프 등으로 설정하면 실제로 중복 오퍼레이션 수행은 하지만, 저장은 못한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-06-18 오후 4.51.18.png&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;2268&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NE00L/btsH2DCk56v/JKp4sakTA6xVDR9eQkFET1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NE00L/btsH2DCk56v/JKp4sakTA6xVDR9eQkFET1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NE00L/btsH2DCk56v/JKp4sakTA6xVDR9eQkFET1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNE00L%2FbtsH2DCk56v%2FJKp4sakTA6xVDR9eQkFET1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1448&quot; height=&quot;2268&quot; data-filename=&quot;스크린샷 2024-06-18 오후 4.51.18.png&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;2268&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 3가지 방법을 정리한 구성 그림&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-06-18 오후 4.51.31.png&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;684&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bUrf02/btsH3jpM6GE/kIHzvnkemGiQmZQsM7miPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bUrf02/btsH3jpM6GE/kIHzvnkemGiQmZQsM7miPK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bUrf02/btsH3jpM6GE/kIHzvnkemGiQmZQsM7miPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbUrf02%2FbtsH3jpM6GE%2FkIHzvnkemGiQmZQsM7miPK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1448&quot; height=&quot;684&quot; data-filename=&quot;스크린샷 2024-06-18 오후 4.51.31.png&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;684&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;참고:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;a href=&quot;https://tech.kakao.com/posts/602&quot;&gt;https://tech.kakao.com/posts/602&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1718697173131&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;신뢰성 있는 카프카 애플리케이션을 만드는 3가지 방법 / 제3회 Kakao Tech Meet - tech.kakao.com&quot; data-og-description=&quot;9월 14일에 진행한 제3회 Kakao Tech Meet의 발표 영상과 발표자 이...&quot; data-og-host=&quot;tech.kakao.com&quot; data-og-source-url=&quot;https://tech.kakao.com/posts/602&quot; data-og-url=&quot;https://tech.kakao.com/posts/602&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/zwjl5/hyWoNhZK9V/VB95MAFv6hSVgUN9oGVBh0/img.png?width=896&amp;amp;height=504&amp;amp;face=660_106_724_177,https://scrap.kakaocdn.net/dn/cQitMl/hyWoIOymoz/ZYufMHaGaJDukGhh3l9FkK/img.png?width=896&amp;amp;height=504&amp;amp;face=660_106_724_177,https://scrap.kakaocdn.net/dn/zhZn4/hyWljv49k8/ip55rRy1NX4v19I6rOMrZ0/img.png?width=896&amp;amp;height=504&amp;amp;face=660_106_724_177&quot;&gt;&lt;a href=&quot;https://tech.kakao.com/posts/602&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://tech.kakao.com/posts/602&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/zwjl5/hyWoNhZK9V/VB95MAFv6hSVgUN9oGVBh0/img.png?width=896&amp;amp;height=504&amp;amp;face=660_106_724_177,https://scrap.kakaocdn.net/dn/cQitMl/hyWoIOymoz/ZYufMHaGaJDukGhh3l9FkK/img.png?width=896&amp;amp;height=504&amp;amp;face=660_106_724_177,https://scrap.kakaocdn.net/dn/zhZn4/hyWljv49k8/ip55rRy1NX4v19I6rOMrZ0/img.png?width=896&amp;amp;height=504&amp;amp;face=660_106_724_177');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;신뢰성 있는 카프카 애플리케이션을 만드는 3가지 방법 / 제3회 Kakao Tech Meet - tech.kakao.com&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;9월 14일에 진행한 제3회 Kakao Tech Meet의 발표 영상과 발표자 이...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;tech.kakao.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Middleware/Kafka&amp;amp;RabbitMQ</category>
      <category>Ack</category>
      <category>acknowledge</category>
      <category>Consumer</category>
      <category>Kafka</category>
      <category>Producer</category>
      <category>멱등성</category>
      <category>카프카</category>
      <author>여성게</author>
      <guid isPermaLink="true">https://coding-start.tistory.com/437</guid>
      <comments>https://coding-start.tistory.com/437#entry437comment</comments>
      <pubDate>Tue, 18 Jun 2024 16:53:07 +0900</pubDate>
    </item>
    <item>
      <title>Springboot + koltin coroutine 사용법</title>
      <link>https://coding-start.tistory.com/435</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;719&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbT0Sw/btsHqdYdE8q/zauPl1f1LjKoXpUYVUxOVK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbT0Sw/btsHqdYdE8q/zauPl1f1LjKoXpUYVUxOVK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbT0Sw/btsHqdYdE8q/zauPl1f1LjKoXpUYVUxOVK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbT0Sw%2FbtsHqdYdE8q%2FzauPl1f1LjKoXpUYVUxOVK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;719&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;719&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;1. 코루틴이 왜 필요할까?&lt;/h4&gt;
&lt;blockquote style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot; data-ke-style=&quot;style3&quot;&gt;&lt;u&gt;&lt;b&gt;코루틴&lt;/b&gt;&lt;/u&gt;은 비동기 프로그래밍을 단순화하고, 효율적인 동시성(concurrency) 관리를 가능하게 하는 프로그래밍 개념입니다. 코루틴은 경량 스레드(lightweight thread)와 같이 작동하는데, 개발자가 프로그램의 어느 시점에서든 실행을 일시 중지하고 필요할 때 재개할 수 있게 해줍니다. 이러한 특성 덕분에 코루틴은 네트워크 호출, 데이터베이스 트랜잭션과 같은 비동기 작업을 쉽고 효과적으로 처리할 수 있으며, 이런 작업들을 마치 동기적 코드처럼 보이게 만들어 줍니다.&lt;br /&gt;&lt;br /&gt;&lt;u&gt;&lt;b&gt;필요성&lt;/b&gt;&lt;/u&gt;은 비동기 코드를 더 간결하고 이해하기 쉽게 만들며, 자원을 효율적으로 사용할 수 있습니다. 전통적인 멀티 스레딩 접근 방식에 비해 메모리 사용을 줄이고, 컨텍스트 스위칭의 오버헤드를 감소시킬 수 있어 애플리케이션의 성능을 향상시킬 수 있습니다.&lt;br /&gt;&lt;br /&gt;&lt;i&gt;&lt;b&gt;1. 경량 스레드&lt;/b&gt;&lt;/i&gt;&lt;br /&gt;코루틴은 스레드보다 훨씬 적은 메모리를 사용합니다. 전통적인 스레드는 각각 독립된 메모리 스택을 소유하고, 이는 대략 1MB 정도의 메모리를 차지할 수 있습니다. 반면, 코루틴은 이러한 스레드 스택을 공유하고, 필요한 경우에만 작은 스택 메모리를 할당하여 사용하므로, 수천 개의 코루틴이 동시에 실행되어도 스레드를 사용했을 때보다 훨씬 적은 메모리를 사용합니다.&lt;br /&gt;&lt;br /&gt;&lt;i&gt;&lt;b&gt;2. 컨텍스트 스위칭 감소&lt;/b&gt;&lt;/i&gt;&lt;br /&gt;컨텍스트 스위칭은 CPU가 한 작업에서 다른 작업으로 전환할 때 발생하는 프로세스로, 스레드 간의 컨텍스트 스위칭은 상당한 오버헤드를 유발할 수 있습니다. 코루틴을 사용하면, 스레드 수보다 훨씬 많은 코루틴을 동시에 관리할 수 있으며, 코루틴은 사용자가 정의한 지점에서만 스케줄링을 허용하기 때문에 필요할 때만 컨텍스트 스위칭이 발생합니다. 이렇게 컨트롤되고 예측 가능한 스위칭은 시스템 자원의 효율적 사용을 가능하게 하며, 전체적인 성능을 향상시킵니다.&lt;br /&gt;(쓰레드가 바뀌어도 실행 정보를 코루틴이 모두 가지고 있기 때문에.)&lt;br /&gt;&lt;br /&gt;&lt;i&gt;&lt;b&gt;3. 더 효율적인 비동기 프로그래밍&lt;/b&gt;&lt;/i&gt;&lt;br /&gt;코틀린 코루틴은 비동기 프로그래밍을 보다 쉽게 만들어, 비동기 함수를 동기적인 방식으로 작성할 수 있게 합니다. 이는 코드의 복잡성과 버그가 발생할 확률을 줄여줍니다. 비동기 작업에서 코루틴은 작업이 완료될 때까지 스레드를 차단하지 않고, 대신 코드의 특정 부분을 일시 중단하여 리소스가 필요할 때만 사용합니다. 이는 리소스 사용과 전력 소비를 줄이고, 애플리케이션의 반응성을 향상시킵니다.&lt;br /&gt;&lt;br /&gt;&lt;i&gt;&lt;b&gt;코틀린에서 코루틴의 특별한 점&lt;br /&gt;&lt;/b&gt;&lt;/i&gt;ㄴ&lt;b&gt;&lt;/b&gt;&lt;u&gt;&lt;b&gt;코틀린&lt;/b&gt;&lt;/u&gt;은 코루틴을 언어 수준에서 지원합니다. 코틀린의 코루틴은 몇 가지 특별한 점을 가지고 있습니다&lt;br /&gt;&lt;br /&gt;1.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;i&gt;&lt;b&gt;간결성&lt;/b&gt;&lt;/i&gt;: 코틀린은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;u&gt;&lt;b&gt;suspend&lt;/b&gt;&lt;/u&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;키워드를 사용하여 비동기 함수를 쉽게 선언할 수 있게 지원하며, 이는 코드를 간결하게 유지하면서도 강력한 비동기 처리를 가능하게 합니다.&lt;br /&gt;&lt;br /&gt;2.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;i&gt;&lt;b&gt;구조화된 동시성&lt;/b&gt;&lt;/i&gt;: 코틀린의 코루틴은 스코프를 기반으로 동작하므로, 코루틴의 생명주기가 그 스코프에 종속됩니다. 이는 리소스를 안전하게 관리하도록 하며, 메모리 누수나 다른 문제점을 방지하는 데 도움을 줍니다.&lt;br /&gt;&lt;br /&gt;3.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;i&gt;&lt;b&gt;콘텍스트 관리&lt;/b&gt;&lt;/i&gt;: 코틀린 코루틴은 실행 콘텍스트(예: 디스패처)를 명시적으로 지정할 수 있어, 개발자가 세밀하게 작업을 제어할 수 있습니다. 이는 UI 작업이나 백그라운드 작업을 적절히 처리할 때 특히 유용합니다.&lt;br /&gt;&lt;br /&gt;4.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;i&gt;&lt;b&gt;쉬운 통합과 확장&lt;/b&gt;&lt;/i&gt;: 코틀린 코루틴은 리액티브 프로그래밍 모델과도 잘 통합되며, 기존의 콜백 기반 라이브러리나 프레임워크와도 쉽게 결합할 수 있어, 기존 코드베이스로의 증분 도입이 용이합니다. 코틀린 코루틴을 배우는 것은 현대의 비동기 및 동시성 프로그래밍 요구에 부응할 수 있는 현명한 선택입니다. 개발자로서 코드의 유지보수성을 높이고, 성능을 개선하며, 다양한 환경과 상황에서 더 나은 사용자 경험을 제공하기 위해 코루틴을 습득하는 것이 중요합니다.&lt;/blockquote&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;2. 중단(suspend)이란 무엇이며 어떻게 작동할까?&lt;/h4&gt;
&lt;blockquote style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot; data-ke-style=&quot;style3&quot;&gt;&lt;i&gt;&lt;b&gt;중단(suspend)이란 무엇인가?&lt;/b&gt;&lt;/i&gt;&lt;br /&gt;중단이란 코틀린의 코루틴에서 사용되는 키워드로, 실행을 일시 중지할 수 있게 해주는 프로그래밍 메커니즘입니다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;u&gt;&lt;b&gt;suspend&lt;/b&gt;&lt;/u&gt;라는 키워드는 특정 함수 앞에 위치하여 해당 함수가 비동기적으로 동작하며, 필요한 경우 실행을 멈추고 나중에 다시 시작할 수 있음을 의미합니다. 이러한 중단 가능 함수는 리소스가 바쁘다거나 응답을 기다리는 등의 이유로 즉시 완료할 수 없는 작업에 유용합니다.&lt;br /&gt;&lt;br /&gt;&lt;i&gt;&lt;b&gt;중단 함수의 작동 원리&lt;/b&gt;&lt;/i&gt;&lt;br /&gt;중단 함수는 코루틴의 실행을 &amp;lsquo;일시 중지&amp;rsquo;할 수 있는 지점을 제공합니다. 이 함수들은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;u&gt;&lt;b&gt;suspend&lt;/b&gt;&lt;/u&gt;&amp;nbsp;키워드를 통해 정의되며, 코루틴 스케줄러의 관리 하에 실행됩니다. 중단 함수 내에서, 함수는 백그라운드 작업(예: 네트워크 요청, 긴 계산 등)이 완료될 때까지 코루틴의 실행을 중지할 수 있고, 그 실행은 호출 스택을 차단하지 않으면서도 나중에 자동으로 재개됩니다. 중단 지점에서 코드의 실행이 멈춘 후, 코루틴은 백그라운드 작업의 완료를 기다리는 동안 다른 작업을 처리할 수 있는 자원(스레드 등)을 해제합니다. 코틀린 컴파일러는 중단 함수를 처리할 때 내부적으로 상태 머신을 구축합니다. 이 상태 머신은 함수의 중단과 재개 지점을 관리합니다. 함수가 중단되면, 상태 머신은 해당 시점과 변수의 상태를 저장하고, 작업이 완료되면 저장된 상태에서 다시 작업을 재개할 수 있도록 합니다.&lt;br /&gt;&lt;br /&gt;&lt;i&gt;&lt;b&gt;코루틴과 스레드의 상호 작용&lt;/b&gt;&lt;/i&gt;&lt;br /&gt;코루틴은 스레드와는 독립적으로 작동하지만, 실행을 위해서는 스레드가 필요합니다. 코틀린의 코루틴은 기본적으로 스레드 위에서 실행되지만, 스레드와 달리 적은 비용으로 생성 및 관리가 가능하며, 수천 개의 코루틴이 단일 또는 소수의 스레드에서 실행될 수 있습니다. 코루틴은 스레드를 차지하는 것이 아니라, 필요할 때만 스레드의 자원을 사용하여 실행되고, 필요 없을 때는 자원을 반환합니다. 이는 스레드의 효율적 사용을 가능하게 하며, 애플리케이션의 성능 향상에 기여합니다.&lt;br /&gt;&lt;br /&gt;요약하자면, 중단 함수와 코루틴의 상호 작용은 효율적인 동시성 관리와 비동기 태스크의 간소화를 가능하게 하며, 이는 현대 소프트웨어 개발에서 중요한 요소입니다. 이러한 기능을 통해 코틀린은 개발자가 보다 쉽고 효과적으로 비동기 로직을 구현할 수 있게 해 줍니다.&lt;/blockquote&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그래서 중단 시키는 게 뭔데?&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_Photo_2024-05-16-15-01-25.jpeg&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1440&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbAFz2/btsHqrvzlWk/R9sOsJKUc3jkMgQBfbkwgK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbAFz2/btsHqrvzlWk/R9sOsJKUc3jkMgQBfbkwgK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbAFz2/btsHqrvzlWk/R9sOsJKUc3jkMgQBfbkwgK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbAFz2%2FbtsHqrvzlWk%2FR9sOsJKUc3jkMgQBfbkwgK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;1440&quot; data-filename=&quot;KakaoTalk_Photo_2024-05-16-15-01-25.jpeg&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1440&quot;/&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;즉 코루틴 빌더(launch, async 등)을 이용해 suspend 함수를 사용하면 결국은 새로운 코루틴이 생기는 것인데, 그때 그 함수를 호출한 호출자는 잠시 실행을 멈춘다. (이 또한 다른 코루틴이고 다른 스레드에서 실행되고 있을 테니까) 실행을 멈추게 되면 사용하고 있던 스레드는 반납되고 호출된 새로운 코루틴이 작업을 시작한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일시정지 될 때는 Continuation 객체를 반환하는데, Continuation 객체는 멈췄던 곳에서 다시 코루틴을 실행할 수 있다.(다른 Thread에서 실행 될 수도 있음) 여기서 Thread와 코루틴의 차이라면, Thread는 저장이 불가능하고, 멈추는 것만 가능하다. 하지만 Continuation은 멈추는 것은 물론 이전의 실행 상태를 저장하고 있다.(컨텍스트 스위칭 비용이 줄어드는 것이 여기서 나오는 것이다.)&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;3. 코루틴의 실제 구현&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;1. suspend 함수는 함수가 시작할 때와 suspend 함수가 호출되었을 때 상태를 가진다는 점에서 상태 머신(state machine)과 비슷하다.&lt;br /&gt;&lt;br /&gt;2. Continuation 객체는 상태를 나타내는 숫자와 로컬 데이터를 가지고 있다.&lt;br /&gt;&lt;br /&gt;3. 함수의 Continuation 객체가 이 함수를 부르는 다른 함수의 Continuation 객체를 decorate한다. 그 결과, 모든 Continuation 객체는 실행을 재개하거나 재개된 함수를 완료할 때 사용되는 콜 스택으로 사용된다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1715842249549&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Test
fun main() = runTest {
    println(&quot;Before&quot;)
        // 코루틴 실행을 멈춘다.
    val value = suspendCoroutine&amp;lt;String&amp;gt; { continuation -&amp;gt;
        Thread.sleep(1000)
        // 리턴할 결과를 만들었으면 자신을 호출한 컨티뉴에이션에게 결과값과 함께 재개(resume)한다.
        continuation.resumeWith(Result.success(&quot;SuspendFunction&quot;))
    }
    println(value)
    println(&quot;After&quot;)
}

//Before
//(1초 후)
//SuspendFunction
//After&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;위 코드를 보고 설명하자면, suspendCoroutine으로 시작하는 코드 라인에서 이 함수를 호출한 main()함수의 실행은 중지된다. 이때 main함수를 실행 중이던 쓰레드는 계속 기다리지 않고 다른 일을 하러 가고, suspendCoroutine 실행이 모두 완료되면 호출한 코루틴의 Continuation에게 결과 값을 넘기고 resume한다. 그러면 멈춘 지점부터 실행을 재개하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드는 suspend 함수 예제로 실제 유사 구현을 보자면 아래와 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1715902373663&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 우리가 작성한 함수
suspend fun myFunction() {
    println(&quot;Before&quot;)
    // suspend function
    delay(1000)
    println(&quot;After&quot;)
}

// 컴파일 후 작성된 코드
fun myFunction(continuation: Continuation&amp;lt;Unit&amp;gt;): Any {
	// 호출자 코루틴의 Continuation을 decorate
    val continuation = continuation as? MyContinuation?: MyContinuation(continuation)
	
    // suspend function이 등장하는 시점마다 label을 하나씩 증가시킨다.
    if (continuation.label == 0) {
        println(&quot;Before&quot;)
        continuation.label = 1
        if (delay(1000, continuation) == COROUTINE_SUSPENDED) {
            return COROUTINE_SUSPENDED
        }
    }
    
    // label 값을 통해 중지 후 재실행되는 지점을 관리한다.
    if (continuation.label == 1) {
        println(&quot;After&quot;)
        return Unit
    }
    error(&quot;Impossible&quot;)
}

class MyContinuation(
    val completion: Continuation&amp;lt;Unit&amp;gt;
): Continuation&amp;lt;Unit&amp;gt; {
    override val context: CoroutineContext
        get() = completion.context

    var label = 0
    var result: Result&amp;lt;Any&amp;gt;? = null

    override fun resumeWith(result: Result&amp;lt;Unit&amp;gt;) {
        this.result = result
        var res = try {
            val r = myFunction(this)
            if (r == COROUTINE_SUSPENDED) return
            Result.success(r as Unit)
        } catch (e: Throwable) {
            Result.failure(e)
        }
        completion.resumeWith(res)
    }
}&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;결론적으로 위 코드를 보면 코루틴마다 Continuation 객체를 갖게되는데, 그 Continuation 객체에는 일시정지된 위치값(label) 그리고 다른 코루틴의 결과 값(local variable) 등을 가져서 일시 중지된 부분부터 재실행이 가능하다. 즉, 일단 쓰레드 모델처럼 콜스택에 실행 정보를 가지고 있어서 매번 컨텍스트 스위칭을 하는 것이 아니라 코루틴 자체적으로 실행정보를 가지기에 불필요한 쓰레드간 컨텍스트 스위칭이 발생하지 않는다.&lt;/p&gt;
&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>Web/Spring</category>
      <category>continuation</category>
      <category>coroutine</category>
      <category>kotlin</category>
      <category>Suspend</category>
      <category>코루틴</category>
      <category>코틀린</category>
      <author>여성게</author>
      <guid isPermaLink="true">https://coding-start.tistory.com/435</guid>
      <comments>https://coding-start.tistory.com/435#entry435comment</comments>
      <pubDate>Thu, 16 May 2024 15:07:50 +0900</pubDate>
    </item>
    <item>
      <title>Spring kafka + micrometer tracing</title>
      <link>https://coding-start.tistory.com/434</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;build.gradle&lt;/h4&gt;
&lt;pre id=&quot;code_1715684333145&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
    id(&quot;org.springframework.boot&quot;) version &quot;3.2.5&quot;
    id(&quot;io.spring.dependency-management&quot;) version &quot;1.1.4&quot;
    kotlin(&quot;jvm&quot;) version &quot;1.9.23&quot;
    kotlin(&quot;plugin.spring&quot;) version &quot;1.9.23&quot;
}

group = &quot;com.spring&quot;
version = &quot;0.0.1-SNAPSHOT&quot;

val jvmVersion = JavaVersion.VERSION_17

java {
    sourceCompatibility = jvmVersion
}

repositories {
    mavenCentral()
}

dependencies {
    implementation(&quot;org.springframework.boot:spring-boot-starter-web&quot;)
    implementation(&quot;com.fasterxml.jackson.module:jackson-module-kotlin&quot;)
    implementation(&quot;org.jetbrains.kotlin:kotlin-reflect&quot;)
    implementation(&quot;org.springframework.kafka:spring-kafka&quot;)
    implementation(&quot;org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3&quot;)

    implementation(&quot;org.springframework.boot:spring-boot-starter-actuator&quot;)

    // tracing
    implementation(&quot;io.micrometer:micrometer-tracing-bridge-brave&quot;)
    implementation(&quot;io.zipkin.reporter2:zipkin-reporter-brave&quot;)
    implementation(&quot;io.micrometer:micrometer-registry-prometheus&quot;)

    // test tracing
    testImplementation(&quot;io.micrometer:micrometer-tracing-test&quot;)
    testImplementation(&quot;io.micrometer:micrometer-tracing-integration-test&quot;)

    testImplementation(&quot;org.springframework.boot:spring-boot-starter-test&quot;)
    testImplementation(&quot;org.springframework.kafka:spring-kafka-test&quot;)
    testImplementation(&quot;org.mockito.kotlin:mockito-kotlin:5.0.0&quot;)
    testApi(&quot;org.mockito:mockito-inline:3.6.28&quot;)
}

tasks.withType&amp;lt;KotlinCompile&amp;gt; {
    kotlinOptions {
        freeCompilerArgs = listOf(&quot;-Xjsr305=strict&quot;)
        jvmTarget = jvmVersion.toString()
    }
}

tasks.withType&amp;lt;Test&amp;gt; {
    useJUnitPlatform()
}&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;application.yml&lt;/h4&gt;
&lt;pre id=&quot;code_1715684372961&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring:
  application:
    name: kafka-sample

kafka:
  brokers: localhost:9092
  topic: test-topic
  group-id: test-group-id
  max-poll-records: 3
  enable-auto-commit: false
  auto-offset-reset: latest

management:
  zipkin:
    tracing:
      endpoint: http://localhost:9411/api/v2/spans # default
  tracing:
    sampling:
      probability: 1.0
    propagation:
      type: b3_multi
    enabled: true&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;KafkaConfig.kt&lt;/h4&gt;
&lt;pre id=&quot;code_1715684422182&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.spring.kafkasample.config

import org.apache.kafka.clients.consumer.ConsumerConfig
import org.apache.kafka.clients.producer.ProducerConfig
import org.apache.kafka.common.serialization.StringDeserializer
import org.apache.kafka.common.serialization.StringSerializer
import org.springframework.beans.factory.annotation.Value
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.kafka.annotation.EnableKafka
import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory
import org.springframework.kafka.core.*
import org.springframework.kafka.listener.ContainerProperties
import org.springframework.kafka.support.micrometer.KafkaListenerObservation
import org.springframework.kafka.support.micrometer.KafkaTemplateObservation


@EnableKafka
@Configuration
class KafkaConfig(
    @Value(&quot;\${kafka.brokers}&quot;)
    private val brokers: String,
    @Value(&quot;\${kafka.enable-auto-commit}&quot;)
    private val enableAutoCommit: Boolean,
    @Value(&quot;\${kafka.max-poll-records}&quot;)
    private val maxPollRecords: Int,
    @Value(&quot;\${kafka.auto-offset-reset}&quot;)
    private val autoOffsetReset: String,
    private val kafkaErrorHandler: KafkaErrorHandler
) {
    @Bean
    fun kafkaListenerContainerFactory(): ConcurrentKafkaListenerContainerFactory&amp;lt;String, String&amp;gt; {
        val factory = ConcurrentKafkaListenerContainerFactory&amp;lt;String, String&amp;gt;()
        factory.consumerFactory = consumerFactory()
        factory.setConcurrency(1)
        # observation config
        factory.containerProperties.observationConvention = KafkaListenerObservation.DefaultKafkaListenerObservationConvention.INSTANCE
        factory.containerProperties.isObservationEnabled = true
        factory.containerProperties.ackMode = ContainerProperties.AckMode.MANUAL
        factory.setCommonErrorHandler(kafkaErrorHandler)
        return factory
    }

    @Bean
    fun consumerFactory(): ConsumerFactory&amp;lt;String, String&amp;gt; {
        return DefaultKafkaConsumerFactory(consumerConfigs())
    }

    @Bean
    fun consumerConfigs(): Map&amp;lt;String, Any&amp;gt; {
        val props = mutableMapOf&amp;lt;String, Any&amp;gt;()
        props[ProducerConfig.BOOTSTRAP_SERVERS_CONFIG] = brokers
        props[ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG] = StringDeserializer::class.java
        props[ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG] = StringDeserializer::class.java
        props[ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG] = enableAutoCommit
        props[ConsumerConfig.AUTO_OFFSET_RESET_CONFIG] = autoOffsetReset
        props[ConsumerConfig.MAX_POLL_RECORDS_CONFIG] = maxPollRecords
        return props
    }

    @Bean
    fun kafkaTemplate(): KafkaTemplate&amp;lt;String, String&amp;gt; {
        val kafkaTemplate = KafkaTemplate(producerFactory())
        # observation config
        kafkaTemplate.setObservationEnabled(true)
        kafkaTemplate.setObservationConvention(KafkaTemplateObservation.DefaultKafkaTemplateObservationConvention.INSTANCE)
        return kafkaTemplate
    }

    @Bean
    fun producerFactory():ProducerFactory&amp;lt;String, String&amp;gt; {
        val config = mutableMapOf&amp;lt;String, Any&amp;gt;()
        config[ProducerConfig.BOOTSTRAP_SERVERS_CONFIG] = brokers
        config[ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG] = StringSerializer::class.java
        config[ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG] = StringSerializer::class.java

        return DefaultKafkaProducerFactory(config)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나머지 consumer, 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;&lt;a href=&quot;https://coding-start.tistory.com/432&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://coding-start.tistory.com/432&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1715684525366&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Kafka - Spring kafka Producer, Consumer 예제 코드 및 오프셋 커밋, 에러 핸들링 설명&quot; data-og-description=&quot;아래는 ContainerFactory 및 conusmer, producer 설정들이다. package com.spring.kafkasample.config import org.apache.kafka.clients.consumer.ConsumerConfig import org.apache.kafka.clients.producer.ProducerConfig import org.apache.kafka.common.serializ&quot; data-og-host=&quot;coding-start.tistory.com&quot; data-og-source-url=&quot;https://coding-start.tistory.com/432&quot; data-og-url=&quot;https://coding-start.tistory.com/432&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cYdume/hyV2vXMUDu/kPK6SZ3tgA2ikyRkH07kWK/img.png?width=800&amp;amp;height=537&amp;amp;face=0_0_800_537,https://scrap.kakaocdn.net/dn/di9bCW/hyV6dupyDQ/8GcZALyN5O4dA78pD6KabK/img.png?width=800&amp;amp;height=537&amp;amp;face=0_0_800_537,https://scrap.kakaocdn.net/dn/xJVDA/hyV2DIiRh8/k4i78DcR92DYoMF5OPfR1k/img.png?width=1280&amp;amp;height=860&amp;amp;face=0_0_1280_860&quot;&gt;&lt;a href=&quot;https://coding-start.tistory.com/432&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://coding-start.tistory.com/432&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cYdume/hyV2vXMUDu/kPK6SZ3tgA2ikyRkH07kWK/img.png?width=800&amp;amp;height=537&amp;amp;face=0_0_800_537,https://scrap.kakaocdn.net/dn/di9bCW/hyV6dupyDQ/8GcZALyN5O4dA78pD6KabK/img.png?width=800&amp;amp;height=537&amp;amp;face=0_0_800_537,https://scrap.kakaocdn.net/dn/xJVDA/hyV2DIiRh8/k4i78DcR92DYoMF5OPfR1k/img.png?width=1280&amp;amp;height=860&amp;amp;face=0_0_1280_860');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Kafka - Spring kafka Producer, Consumer 예제 코드 및 오프셋 커밋, 에러 핸들링 설명&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;아래는 ContainerFactory 및 conusmer, producer 설정들이다. package com.spring.kafkasample.config import org.apache.kafka.clients.consumer.ConsumerConfig import org.apache.kafka.clients.producer.ProducerConfig import org.apache.kafka.common.serializ&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;coding-start.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Web/Spring</category>
      <category>Kafka</category>
      <category>micrometer</category>
      <category>observation</category>
      <category>springboot 3</category>
      <category>Tracer</category>
      <category>tracing</category>
      <author>여성게</author>
      <guid isPermaLink="true">https://coding-start.tistory.com/434</guid>
      <comments>https://coding-start.tistory.com/434#entry434comment</comments>
      <pubDate>Tue, 14 May 2024 20:03:51 +0900</pubDate>
    </item>
    <item>
      <title>Micrometer Tracing</title>
      <link>https://coding-start.tistory.com/433</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;600&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bIbsiV/btsG35kPCYv/Vto8jnWStts8ucyN0WnMa0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bIbsiV/btsG35kPCYv/Vto8jnWStts8ucyN0WnMa0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bIbsiV/btsG35kPCYv/Vto8jnWStts8ucyN0WnMa0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbIbsiV%2FbtsG35kPCYv%2FVto8jnWStts8ucyN0WnMa0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1200&quot; height=&quot;600&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;600&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기본 사용법&lt;/h3&gt;
&lt;pre id=&quot;code_1714440751183&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Span을 생성한다. 만약 현재 쓰레드에 스팬이 있다면, 현재 새로 생성한 newSpan의 부모가 된다.
Span newSpan = this.tracer.nextSpan().name(&quot;calculateTax&quot;);

// Span을 시작하고 Scope에 넣는다.(Scope에 넣는다는 의미는 Thread local에 스팬을 넣는다는 뜻)
try (Tracer.SpanInScope ws = this.tracer.withSpan(newSpan.start())) {
    // 더 나은 디버깅을 위해서 Span에 key/value 쌍을 넣을 수 있다.
    newSpan.tag(&quot;taxValue&quot;, taxValue);

    // Span에 이벤트 로깅을 할 수 있다.(이벤트에는 타임스탬프가 찍힌다.)
    newSpan.event(&quot;taxCalculated&quot;);
}
finally {
    // Span을 닫는 것을 항상 기억해야 한다. Span을 닫아야 분산 트레이싱 시스템에 Span을 collecting된다.
    newSpan.end();
}&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;/h3&gt;
&lt;pre id=&quot;code_1714441949285&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Span spanFromThreadX = this.tracer.nextSpan().name(&quot;calculateTax&quot;);
try (Tracer.SpanInScope ws = this.tracer.withSpan(spanFromThreadX.start())) {
    executorService.submit(() -&amp;gt; {
        // Pass the span from thread X
        Span continuedSpan = spanFromThreadX;
        // ...
        // You can tag a span
        continuedSpan.tag(&quot;taxValue&quot;, taxValue);
        // ...
        // You can log an event on a span
        continuedSpan.event(&quot;taxCalculated&quot;);
    }).get();
}
finally {
    spanFromThreadX.end();
}&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;부모 Span을 알고 있을 때 명시적으로 자식 Span을 생성하는 방법&lt;/h3&gt;
&lt;pre id=&quot;code_1714442122145&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// let's assume that we're in a thread Y and we've received
// the `initialSpan` from thread X. `initialSpan` will be the parent
// of the `newSpan`
Span newSpan = this.tracer.nextSpan(initialSpan).name(&quot;calculateCommission&quot;);
// ...
// You can tag a span
newSpan.tag(&quot;commissionValue&quot;, commissionValue);
// ...
// You can log an event on a span
newSpan.event(&quot;commissionCalculated&quot;);
// Once done remember to end the span. This will allow collecting
// the span to send it to e.g. Zipkin. The tags and events set on the
// newSpan will not be present on the parent
newSpan.end();&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;Micrometer tracing Brave 세팅방법&lt;/h3&gt;
&lt;pre id=&quot;code_1715227596928&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# build.gradle.kts
// tracing
implementation(&quot;io.micrometer:micrometer-tracing-bridge-brave&quot;)
implementation(&quot;io.zipkin.reporter2:zipkin-reporter-brave&quot;)
// test tracing
testImplementation(&quot;io.micrometer:micrometer-tracing-test&quot;)
testImplementation(&quot;io.micrometer:micrometer-tracing-integration-test&quot;)

# application.yml
management:
  tracing:
    sampling:
      probability: 1.0
    propagation:
      type: b3_multi
    enabled: true
   zipkin:
    tracing:
      endpoint: ~&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;Skip pattern&lt;/h3&gt;
&lt;pre id=&quot;code_1715227668258&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Configuration
class TraceSkipPatternConfig {
    @Bean
    fun serverContextPredicate(): ObservationPredicate {
        val skipRegularExpressions = &quot;/health-check|/actuator/.*&quot;
            .split(&quot;|&quot;)
            .map { it.toRegex() }
            .toSet()

        return ObservationPredicate { _, context -&amp;gt;
            if (context !is ServerRequestObservationContext) {
                return@ObservationPredicate true
            }

            val servletRequest = context.carrier
            val path = servletRequest.requestURI

            return@ObservationPredicate skipRegularExpressions.none { p -&amp;gt; path.matches(p) }
        }
    }

    @Bean
    fun noSpringSecurityObservations(): ObservationPredicate {
        return ObservationPredicate { name, _ -&amp;gt;
            !name.startsWith(&quot;spring.security.&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;a href=&quot;https://github.com/micrometer-metrics/micrometer-samples&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Micrometer tracing sample code git&lt;/a&gt;&lt;/p&gt;</description>
      <category>Web/Spring</category>
      <category>distribute log</category>
      <category>micrometer</category>
      <category>scope</category>
      <category>span</category>
      <category>Trace</category>
      <category>tracing</category>
      <category>분산 추적</category>
      <author>여성게</author>
      <guid isPermaLink="true">https://coding-start.tistory.com/433</guid>
      <comments>https://coding-start.tistory.com/433#entry433comment</comments>
      <pubDate>Tue, 30 Apr 2024 11:05:57 +0900</pubDate>
    </item>
    <item>
      <title>Kafka - Spring kafka Producer, Consumer 예제 코드 및 오프셋 커밋, 에러 핸들링 설명</title>
      <link>https://coding-start.tistory.com/432</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;860&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bpZBE0/btsAzCblu30/CcYbtGF0PkgbrjE6G5oxLK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bpZBE0/btsAzCblu30/CcYbtGF0PkgbrjE6G5oxLK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpZBE0/btsAzCblu30/CcYbtGF0PkgbrjE6G5oxLK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbpZBE0%2FbtsAzCblu30%2FCcYbtGF0PkgbrjE6G5oxLK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;860&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;860&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 ContainerFactory 및 conusmer, producer 설정들이다.&lt;/p&gt;
&lt;pre id=&quot;code_1700237847609&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.spring.kafkasample.config

import org.apache.kafka.clients.consumer.ConsumerConfig
import org.apache.kafka.clients.producer.ProducerConfig
import org.apache.kafka.common.serialization.StringDeserializer
import org.apache.kafka.common.serialization.StringSerializer
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.kafka.annotation.EnableKafka
import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory
import org.springframework.kafka.core.*
import org.springframework.kafka.listener.ContainerProperties


@EnableKafka
@Configuration
class KafkaConfig(private val kafkaErrorHandler: KafkaErrorHandler) {
    // RecordListener
    @Bean
    fun kafkaListenerContainerFactory(): ConcurrentKafkaListenerContainerFactory&amp;lt;String, String&amp;gt; {
        val factory = ConcurrentKafkaListenerContainerFactory&amp;lt;String, String&amp;gt;()
        factory.consumerFactory = consumerFactory()
        factory.setConcurrency(1)
        // MANUAL 설정을 해줘야, 리스너에서 Acknowledge 오브젝트를 받을 수 있음.
        factory.containerProperties.ackMode = ContainerProperties.AckMode.MANUAL
        // 컨슈머 로직 처리시 예외 발생시 어떻게 처리할지를 정의
        // default로는 DefaultErrorHandler를 사용하는데 기본 동작구조는 최대 10회 retry 후
        // (backoff ms는 100ms)
        // 실패한 메시지를 커밋해버린다.(물론 커밋을 안하게 할 수 있다. 요건 뒤에서 설명)
        factory.setCommonErrorHandler(kafkaErrorHandler)
        return factory
    }

    // BatchListener
    @Bean
    fun batchKafkaListenerContainerFactory(): ConcurrentKafkaListenerContainerFactory&amp;lt;String, String&amp;gt; {
        val factory = ConcurrentKafkaListenerContainerFactory&amp;lt;String, String&amp;gt;()
        factory.consumerFactory = consumerFactory()
        factory.setConcurrency(1)
        // 배치 리스너를 사용한다는 설정
        factory.isBatchListener = true
        factory.containerProperties.ackMode = ContainerProperties.AckMode.MANUAL
        factory.setCommonErrorHandler(kafkaErrorHandler)
        return factory
    }

    @Bean
    fun consumerFactory(): ConsumerFactory&amp;lt;String, String&amp;gt; {
        return DefaultKafkaConsumerFactory(consumerConfigs())
    }

    @Bean
    fun consumerConfigs(): Map&amp;lt;String, Any&amp;gt; {
        val props = mutableMapOf&amp;lt;String, Any&amp;gt;()
        props[ProducerConfig.BOOTSTRAP_SERVERS_CONFIG] = &quot;0.0.0.0:29092&quot;
        props[ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG] = StringDeserializer::class.java
        props[ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG] = StringDeserializer::class.java
        props[ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG] = false
        props[ConsumerConfig.AUTO_OFFSET_RESET_CONFIG] = &quot;latest&quot;
        // 해당 설정은 한번에 poll()로 몇 개의 레코드를 가져오는지 설정.
        // AckMode.MANUAL일 때 커밋 단위가 되는 개수.
        // poll()로 2개의 레코드를 가져와서 해당 2개의 레코드를 다 처리완료되면
        // 2개가 한번에 커밋됨.
        props[ConsumerConfig.MAX_POLL_RECORDS_CONFIG] = 2
        return props
    }

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

    @Bean
    fun producerFactory():ProducerFactory&amp;lt;String, String&amp;gt; {
        val config = mutableMapOf&amp;lt;String, Any&amp;gt;()
        config[ProducerConfig.BOOTSTRAP_SERVERS_CONFIG] = &quot;0.0.0.0:29092&quot;
        config[ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG] = StringSerializer::class.java
        config[ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG] = StringSerializer::class.java

        return DefaultKafkaProducerFactory(config)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 에러 핸들러 코드이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1700238309226&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.spring.kafkasample.config

import org.apache.kafka.clients.consumer.Consumer
import org.apache.kafka.clients.consumer.ConsumerRecord
import org.springframework.kafka.listener.CommonErrorHandler
import org.springframework.kafka.listener.MessageListenerContainer
import org.springframework.stereotype.Component
import java.lang.Exception

@Component
class KafkaErrorHandler: CommonErrorHandler {
    override fun handleRecord(
        thrownException: Exception,
        record: ConsumerRecord&amp;lt;*, *&amp;gt;,
        consumer: Consumer&amp;lt;*, *&amp;gt;,
        container: MessageListenerContainer
    ) {
        print(&quot;error: ${thrownException.message}, record: ${record.value()}&quot;)
    }

    // 해당 값이 false면 에러 핸들링 이후에도 오프셋 커밋을 하지 않음.
    override fun isAckAfterHandle(): Boolean {
        return true
    }
}&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;(max poll count 개수 만큼 폴링하고 각 메시지를 컨슘해서 커밋하는데, 커밋은 메시지 단위가 아니고 max poll count 만큼 다 처리된 후 한번에 커밋된다. AckMode.MANUAL 일때!)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 에러 핸들링이 끝나면, 커밋이 된다고 하였는데 이 설정이 바로 isAckAfterHandler() 설정이다. 요게 false이면 에러 처리해도 오프셋 커밋이 되지 않음.(이건 당연히 true가 맞는게, 항상 예외가 발생하는 로직일 경우 무한으로 컨슘이 발생하기 때문이다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 만약 한번의 poll()에서 2개를 가져왔고, 첫번째 메시지 처리에서 예외가 발생해서 에러 핸들링이 발생하면 어떻게 될까? 커밋? 아니다. 일단 넘어가고 2번째 메시지까지 처리가 완료되어야(성공이던 예외처리던) 첫번째 예외처리된 메시지도 커밋된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 리스너 설정이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1700238441145&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.spring.kafkasample.config

import com.spring.kafkasample.ProduceController
import org.springframework.kafka.annotation.KafkaListener
import org.springframework.kafka.support.Acknowledgment
import org.springframework.stereotype.Component

@Component
class KafkaListener {
    @KafkaListener(
        clientIdPrefix = &quot;record-listener&quot;,
        topics = [&quot;test-topic&quot;],
        groupId = &quot;test-topic-group&quot;,
        containerFactory = &quot;kafkaListenerContainerFactory&quot;
    )
    fun recordListener(message: ProduceController.Message, acknowledgment: Acknowledgment) {
        print(&quot;message: $message&quot;)
        acknowledgment.acknowledge()
    }

    @KafkaListener(
        clientIdPrefix = &quot;batch-listener&quot;,
        topics = [&quot;test-topic&quot;],
        groupId = &quot;test-topic-group&quot;,
        containerFactory = &quot;batchKafkaListenerContainerFactory&quot;
    )
    fun batchListener(messages: List&amp;lt;ProduceController.Message&amp;gt;, acknowledgment: Acknowledgment) {
        print(&quot;message: $messages&quot;)
        acknowledgment.acknowledge()
    }
}&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;예제에 배치 리스너가 있는데, 배치 리스터는 한번에 poll()으로 가져온 메시지 모두를 인자로 받는 것이다. 위 설정대로라면 배치 리스너는 2개의 메시지를 받을 것이다.(AckMode.MANUAL은 배치 리스너 일때 AckMode.BATCH와 동일하게 동작한다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 하나 살펴보아야할 것이 있다. 스프링 카프카에서는&amp;nbsp; 브로커에서 메시지를 가져올 때, max poll record 만큼 가져오지 않고 fetch max byte size만큼 가져오는데, 만약 fetch로 4개를 가져왔다 가정해보자.&lt;/p&gt;
&lt;p data-ke-size=&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-size=&quot;size16&quot; data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;a,b,c,d -&amp;gt; 1번째 poll()에 a,b를 가져옴 -&amp;gt; a, b를 처리하다 커밋을 하지 못함&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;위와 같은 상황이 발생하였다면 다음 동작은 어떻게 될까? a,b를 다시 컨슘한다? 아니다. 스프링 카프카는 내부적으로 fetch한 데이터의 오프셋을 따로 관리하고 있기때문에 a,b 커밋을 하지 못하더라도 c,d를 가져온다. (물론 보통은 예외가 발생해서 커밋을 못하는 상황일텐데 그럴경우 에러 핸들러에서 처리 후에 isAckAfterHandler() 설정에 의해 커밋이 될것이다.)&lt;/p&gt;</description>
      <category>Middleware/Kafka&amp;amp;RabbitMQ</category>
      <author>여성게</author>
      <guid isPermaLink="true">https://coding-start.tistory.com/432</guid>
      <comments>https://coding-start.tistory.com/432#entry432comment</comments>
      <pubDate>Sat, 18 Nov 2023 01:29:34 +0900</pubDate>
    </item>
    <item>
      <title>spring kafka commit 실패시 next poll때 일어나는일</title>
      <link>https://coding-start.tistory.com/431</link>
      <description>&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/64681806/not-acknowledging-the-kafka-message-at-all-in-manual-immediate-mode&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;https://stackoverflow.com/questions/64681806/not-acknowledging-the-kafka-message-at-all-in-manual-immediate-mode&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;Not acknowledging the kafka message at all in MANUAL_IMMEDIATE mode&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;I could not find any documentation related to this issue, hence the question. What happens if @KafkaListener method does not call acknowledgement.acknowledge() at all when the ackModeis set to&quot; data-og-host=&quot;stackoverflow.com&quot; data-og-source-url=&quot;https://stackoverflow.com/questions/64681806/not-acknowledging-the-kafka-message-at-all-in-manual-immediate-mode&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/Wcmky/hyUruMonua/vs6E6gcuToNeXCdaCsrWWK/img.png?width=316&amp;amp;height=316&amp;amp;face=0_0_316_316&quot; data-og-url=&quot;https://stackoverflow.com/questions/64681806/not-acknowledging-the-kafka-message-at-all-in-manual-immediate-mode&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/64681806/not-acknowledging-the-kafka-message-at-all-in-manual-immediate-mode&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://stackoverflow.com/questions/64681806/not-acknowledging-the-kafka-message-at-all-in-manual-immediate-mode&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/Wcmky/hyUruMonua/vs6E6gcuToNeXCdaCsrWWK/img.png?width=316&amp;amp;height=316&amp;amp;face=0_0_316_316');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Not acknowledging the kafka message at all in MANUAL_IMMEDIATE mode&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;I could not find any documentation related to this issue, hence the question. What happens if @KafkaListener method does not call acknowledgement.acknowledge() at all when the ackModeis set to&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;stackoverflow.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;nack()은 현재 실패한 메시지 전의 오프셋까지 커밋하고 나머지 오프셋메시지를 버리고 다음 폴링때 버린 오프셋부터 다시 폴링한다.&lt;br /&gt;&lt;br /&gt;&lt;a href=&quot;https://pula39.tistory.com/m/19&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;https://pula39.tistory.com/m/19&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;Kafka의 Auto Commit 에서 Auto는 당신이 생각하는 Auto가 아닐 수 있다.&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;환경설정을 할 때 auto라는 키워드가 나오면 작업자는 날먹을 꿈꾸며 행복해지는 한 편, 이 auto가 어디까지 자동으로 해주고 어디까지는 안해주는지 공포에 떨며 작업을 하게 된다. 나에게 kafka의&quot; data-og-host=&quot;pula39.tistory.com&quot; data-og-source-url=&quot;https://pula39.tistory.com/19&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bVTqBJ/hyUrtAgAkT/OobCLeLOAX70G6lr34Pgjk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800&quot; data-og-url=&quot;https://pula39.tistory.com/19&quot;&gt;&lt;a href=&quot;https://pula39.tistory.com/19&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://pula39.tistory.com/19&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bVTqBJ/hyUrtAgAkT/OobCLeLOAX70G6lr34Pgjk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Kafka의 Auto Commit 에서 Auto는 당신이 생각하는 Auto가 아닐 수 있다.&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;환경설정을 할 때 auto라는 키워드가 나오면 작업자는 날먹을 꿈꾸며 행복해지는 한 편, 이 auto가 어디까지 자동으로 해주고 어디까지는 안해주는지 공포에 떨며 작업을 하게 된다. 나에게 kafka의&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;pula39.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;오토 커밋 문제점&lt;/p&gt;</description>
      <category>Middleware/Kafka&amp;amp;RabbitMQ</category>
      <author>여성게</author>
      <guid isPermaLink="true">https://coding-start.tistory.com/431</guid>
      <comments>https://coding-start.tistory.com/431#entry431comment</comments>
      <pubDate>Tue, 7 Nov 2023 22:35:35 +0900</pubDate>
    </item>
    <item>
      <title>Spring kafka 설명</title>
      <link>https://coding-start.tistory.com/430</link>
      <description>&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://jessyt.tistory.com/m/151?category=966697&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;https://jessyt.tistory.com/m/151?category=966697&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;Spring-Kafka Lifecycle&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;이번 글에서는 Spring-Kafka의 Lifecycle에 대해서 작성해보겠습니다. 목차 Lifecycle Lifecycle Management 주의사항 1. Lifecycle @KafkaListener는 Application Context 안에 Bean이 아닙니다. @KafkaListener는 KafkaListenerEndpointR&quot; data-og-host=&quot;jessyt.tistory.com&quot; data-og-source-url=&quot;https://jessyt.tistory.com/151&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/jhjZR/hyUnQo7443/RVh0VqN2NFa4utlcAWVH40/img.png?width=204&amp;amp;height=301&amp;amp;face=0_0_204_301&quot; data-og-url=&quot;https://jessyt.tistory.com/151&quot;&gt;&lt;a href=&quot;https://jessyt.tistory.com/151&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://jessyt.tistory.com/151&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/jhjZR/hyUnQo7443/RVh0VqN2NFa4utlcAWVH40/img.png?width=204&amp;amp;height=301&amp;amp;face=0_0_204_301');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Spring-Kafka Lifecycle&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;이번 글에서는 Spring-Kafka의 Lifecycle에 대해서 작성해보겠습니다. 목차 Lifecycle Lifecycle Management 주의사항 1. Lifecycle @KafkaListener는 Application Context 안에 Bean이 아닙니다. @KafkaListener는 KafkaListenerEndpointR&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;jessyt.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;해당 포스팅 말고 다음 포스팅에도 읽어볼만한 주제가&lt;br /&gt;많음&lt;br /&gt;&lt;br /&gt;&lt;a href=&quot;https://shining-life.tistory.com/m/3&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;https://shining-life.tistory.com/m/3&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;4.1.3. Receiving Messages - (1) MessageListenerContainer&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;카프카 메시지를 수신하는 방법 두 가지 1. MessageListenerContainer Configuring 2. @KafkaListener 어노테이션을 사용하여, 메시지 리스너를 구현 Message Listeners message listener를 위해 제공되는 8가지 인터페이스&quot; data-og-host=&quot;shining-life.tistory.com&quot; data-og-source-url=&quot;https://shining-life.tistory.com/3&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/d2QIY0/hyUnOLyBzx/6fZWOHE6jNuoYPMDCzvym1/img.jpg?width=600&amp;amp;height=600&amp;amp;face=0_0_600_600&quot; data-og-url=&quot;https://shining-life.tistory.com/3&quot;&gt;&lt;a href=&quot;https://shining-life.tistory.com/3&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://shining-life.tistory.com/3&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/d2QIY0/hyUnOLyBzx/6fZWOHE6jNuoYPMDCzvym1/img.jpg?width=600&amp;amp;height=600&amp;amp;face=0_0_600_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;4.1.3. Receiving Messages - (1) MessageListenerContainer&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;카프카 메시지를 수신하는 방법 두 가지 1. MessageListenerContainer Configuring 2. @KafkaListener 어노테이션을 사용하여, 메시지 리스너를 구현 Message Listeners message listener를 위해 제공되는 8가지 인터페이스&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;shining-life.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;nack() 설명&lt;br /&gt;&lt;br /&gt;&lt;a href=&quot;https://docs.spring.io/spring-cloud-stream-binder-kafka/docs/3.1.3/reference/html/spring-cloud-stream-binder-kafka.html#kafka-consumer-properties&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;https://docs.spring.io/spring-cloud-stream-binder-kafka/docs/3.1.3/reference/html/spring-cloud-stream-binder-kafka.html#kafka-consumer-properties&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;Spring Cloud Stream Kafka Binder Reference Guide&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;This guide describes the Apache Kafka implementation of the Spring Cloud Stream Binder. It contains information about its design, usage, and configuration options, as well as information on how the Stream Cloud Stream concepts map onto Apache Kafka specifi&quot; data-og-host=&quot;docs.spring.io&quot; data-og-source-url=&quot;https://docs.spring.io/spring-cloud-stream-binder-kafka/docs/3.1.3/reference/html/spring-cloud-stream-binder-kafka.html#kafka-consumer-properties&quot; data-og-url=&quot;https://docs.spring.io/spring-cloud-stream-binder-kafka/docs/3.1.3/reference/html/spring-cloud-stream-binder-kafka.html#kafka-consumer-properties&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-cloud-stream-binder-kafka/docs/3.1.3/reference/html/spring-cloud-stream-binder-kafka.html#kafka-consumer-properties&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.spring.io/spring-cloud-stream-binder-kafka/docs/3.1.3/reference/html/spring-cloud-stream-binder-kafka.html#kafka-consumer-properties&quot;&gt;
&lt;div class=&quot;og-image&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Spring Cloud Stream Kafka Binder Reference Guide&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;This guide describes the Apache Kafka implementation of the Spring Cloud Stream Binder. It contains information about its design, usage, and configuration options, as well as information on how the Stream Cloud Stream concepts map onto Apache Kafka specifi&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.spring.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;spring cloud stream kafka docs&lt;br /&gt;&lt;br /&gt;spring kafka streams functional 방식으로 컨슈머를 구성하면 왠지..batch mode가 false 이고 레코드 단위로 컨슘 and 커밋하는듯함. 그래서 일전에 반드시 실패하는 로직 경우 커밋을 하지 못하고 무한 컨슘(이건 더 알아봐야 할듯함)&lt;br /&gt;&lt;br /&gt;일단 위 설명에는 아래와 같이 써있음&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: Noto Serif KR;&quot;&gt;If the ackMode is not set and batch mode is not enabled, RECORD ackMode will be used.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;spring kafka docs&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-kafka/docs/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.spring.io/spring-kafka/docs/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1700219399703&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Index of /spring-kafka/docs&quot; data-og-description=&quot;&quot; data-og-host=&quot;docs.spring.io&quot; data-og-source-url=&quot;https://docs.spring.io/spring-kafka/docs/&quot; data-og-url=&quot;https://docs.spring.io/spring-kafka/docs/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-kafka/docs/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.spring.io/spring-kafka/docs/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Index of /spring-kafka/docs&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.spring.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Web/Spring</category>
      <author>여성게</author>
      <guid isPermaLink="true">https://coding-start.tistory.com/430</guid>
      <comments>https://coding-start.tistory.com/430#entry430comment</comments>
      <pubDate>Sun, 5 Nov 2023 15:16:38 +0900</pubDate>
    </item>
    <item>
      <title>Spring Kafka commit 정책</title>
      <link>https://coding-start.tistory.com/429</link>
      <description>&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-kafka/docs/current/reference/html/#committing-offsets&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;https://docs.spring.io/spring-kafka/docs/current/reference/html/#committing-offsets&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;Spring for Apache Kafka&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;When using Spring for Apache Kafka in a Spring Boot application, the Apache Kafka dependency versions are determined by Spring Boot&amp;rsquo;s dependency management. If you wish to use a different version of kafka-clients or kafka-streams, and use the embedded ka&quot; data-og-host=&quot;docs.spring.io&quot; data-og-source-url=&quot;https://docs.spring.io/spring-kafka/docs/current/reference/html/#committing-offsets&quot; data-og-url=&quot;https://docs.spring.io/spring-kafka/docs/current/reference/html/#committing-offsets&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-kafka/docs/current/reference/html/#committing-offsets&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.spring.io/spring-kafka/docs/current/reference/html/#committing-offsets&quot;&gt;
&lt;div class=&quot;og-image&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Spring for Apache Kafka&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;When using Spring for Apache Kafka in a Spring Boot application, the Apache Kafka dependency versions are determined by Spring Boot&amp;rsquo;s dependency management. If you wish to use a different version of kafka-clients or kafka-streams, and use the embedded ka&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.spring.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;a href=&quot;https://hanseom.tistory.com/m/174&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;https://hanseom.tistory.com/m/174&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;08. 스프링 카프카 컨슈머(Spring Kafka Consumer)&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;스프링 카프카 컨슈머는 기존 컨슈머를 2개의 타입으로 나누고 커밋을 7가지로 나누어 세분화 했습니다. 1. 타입 레코드 리스너(MessageListener): 단 1개의 레코드를 처리합니다. (스프링 카프카 컨슈&quot; data-og-host=&quot;hanseom.tistory.com&quot; data-og-source-url=&quot;https://hanseom.tistory.com/174&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/RCtk5/hyUrtUhczk/HaFkbvitAh8kLFkEkV72i1/img.png?width=403&amp;amp;height=122&amp;amp;face=0_0_403_122&quot; data-og-url=&quot;https://hanseom.tistory.com/174&quot;&gt;&lt;a href=&quot;https://hanseom.tistory.com/174&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://hanseom.tistory.com/174&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/RCtk5/hyUrtUhczk/HaFkbvitAh8kLFkEkV72i1/img.png?width=403&amp;amp;height=122&amp;amp;face=0_0_403_122');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;08. 스프링 카프카 컨슈머(Spring Kafka Consumer)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;스프링 카프카 컨슈머는 기존 컨슈머를 2개의 타입으로 나누고 커밋을 7가지로 나누어 세분화 했습니다. 1. 타입 레코드 리스너(MessageListener): 단 1개의 레코드를 처리합니다. (스프링 카프카 컨슈&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;hanseom.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;BATCH 타입일 경우 poll로 가져온 모든 레코드 처리가 완료된 후에 한번에 커밋한다.&lt;br /&gt;&lt;br /&gt;MANUAL 타입일 경우 커밋을 하면 다음 poll때 커밋한다. 리스너에서 레코드 단위로 처리하게 되어 매번 acknowledge()를 호출하면 BATCH 타입과 동일하게 동작한다.(AcknowledgingMessageListener 또는 BatchAcknowledgingMessageListener를 리스너로 사용해야 한다.)&lt;br /&gt;&lt;br /&gt;MANUAL_IMMEDIATE 타입은 acknowledge() 호출시 즉시 커밋한다.(AcknowledgingMessageListener 또는 BatchAcknowledgingMessageListener를 리스너로 사용해야 한다.)&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-kafka/reference/kafka/receiving-messages/message-listeners.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.spring.io/spring-kafka/reference/kafka/receiving-messages/message-listeners.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1700215060911&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Message Listeners :: Spring Kafka&quot; data-og-description=&quot;When you use a message listener container, you must provide a listener to receive data. There are currently eight supported interfaces for message listeners. The following listing shows these interfaces:&quot; data-og-host=&quot;docs.spring.io&quot; data-og-source-url=&quot;https://docs.spring.io/spring-kafka/reference/kafka/receiving-messages/message-listeners.html&quot; data-og-url=&quot;https://docs.spring.io/spring-kafka/reference/kafka/receiving-messages/message-listeners.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-kafka/reference/kafka/receiving-messages/message-listeners.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.spring.io/spring-kafka/reference/kafka/receiving-messages/message-listeners.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Message Listeners :: Spring Kafka&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;When you use a message listener container, you must provide a listener to receive data. There are currently eight supported interfaces for message listeners. The following listing shows these interfaces:&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.spring.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링 카프카 메시지 리스너 레퍼런스&lt;/p&gt;</description>
      <category>Web/Spring</category>
      <author>여성게</author>
      <guid isPermaLink="true">https://coding-start.tistory.com/429</guid>
      <comments>https://coding-start.tistory.com/429#entry429comment</comments>
      <pubDate>Sun, 5 Nov 2023 14:35:47 +0900</pubDate>
    </item>
    <item>
      <title>대용량 트래픽관련 유튜브 영상들</title>
      <link>https://coding-start.tistory.com/428</link>
      <description>&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://m.youtube.com/watch?v=XBXmHCy1EBA&amp;amp;pp=ygUT64yA7Jqp65-JIO2KuOuemO2UvQ%3D%3D&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;https://m.youtube.com/watch?v=XBXmHCy1EBA&amp;amp;pp=ygUT64yA7Jqp65-JIO2KuOuemO2UvQ%3D%3D&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=XBXmHCy1EBA&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/eXz4n/hyUnJ32Xgq/gmVBaDJfgxvLqtIZNzNGh0/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-origin-width=&quot;1280&quot; data-video-origin-height=&quot;720&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/XBXmHCy1EBA&quot; width=&quot;300&quot; height=&quot;225&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;/figure&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&lt;br&gt;&lt;a href=&quot;https://m.youtube.com/watch?v=qzHjK1-07fI&amp;amp;pp=ygUT64yA7Jqp65-JIO2KuOuemO2UvQ%3D%3D&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;https://m.youtube.com/watch?v=qzHjK1-07fI&amp;amp;pp=ygUT64yA7Jqp65-JIO2KuOuemO2UvQ%3D%3D&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=qzHjK1-07fI&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/2oED4/hyUnPpDaOe/rkNslYSyL3PschQEhEDoQ1/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=936_94_1084_618&quot; data-video-origin-width=&quot;1280&quot; data-video-origin-height=&quot;720&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/qzHjK1-07fI&quot; width=&quot;300&quot; height=&quot;225&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;/figure&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&lt;/p&gt;</description>
      <category>Web/Spring</category>
      <author>여성게</author>
      <guid isPermaLink="true">https://coding-start.tistory.com/428</guid>
      <comments>https://coding-start.tistory.com/428#entry428comment</comments>
      <pubDate>Thu, 2 Nov 2023 22:30:35 +0900</pubDate>
    </item>
    <item>
      <title>[Kafka] Parallel Consumers, 메시지별 병렬 처리</title>
      <link>https://coding-start.tistory.com/427</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://d2.naver.com/helloworld/7181840&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://d2.naver.com/helloworld/7181840&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Middleware/Kafka&amp;amp;RabbitMQ</category>
      <author>여성게</author>
      <guid isPermaLink="true">https://coding-start.tistory.com/427</guid>
      <comments>https://coding-start.tistory.com/427#entry427comment</comments>
      <pubDate>Mon, 30 Oct 2023 15:59:15 +0900</pubDate>
    </item>
    <item>
      <title>Java NIO와 멀티플렉싱 기반 다중 접속 서버 설명</title>
      <link>https://coding-start.tistory.com/426</link>
      <description>&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://jongmin92.github.io/2019/03/03/Java/java-nio/&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;https://jongmin92.github.io/2019/03/03/Java/java-nio/&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;</description>
      <author>여성게</author>
      <guid isPermaLink="true">https://coding-start.tistory.com/426</guid>
      <comments>https://coding-start.tistory.com/426#entry426comment</comments>
      <pubDate>Tue, 10 Oct 2023 22:46:53 +0900</pubDate>
    </item>
    <item>
      <title>Transformer - 어텐션 원리 설명</title>
      <link>https://coding-start.tistory.com/425</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://ratsgo.github.io/nlpbook/docs/language_model/tr_self_attention/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://ratsgo.github.io/nlpbook/docs/language_model/tr_self_attention/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1693384770620&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Self Attention&quot; data-og-description=&quot;pratical tips for Natural Language Processing&quot; data-og-host=&quot;ratsgo.github.io&quot; data-og-source-url=&quot;https://ratsgo.github.io/nlpbook/docs/language_model/tr_self_attention/&quot; data-og-url=&quot;https://ratsgo.github.io/nlpbook/docs/language_model/tr_self_attention/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/qaa9e/hyTMfWBWK0/1cIKDf0K2HqzBkxnvC5LKK/img.png?width=2474&amp;amp;height=1370&amp;amp;face=0_0_2474_1370,https://scrap.kakaocdn.net/dn/xb08U/hyTMcFBKzU/V4amR3QbPvQMoEPKsOA9p0/img.png?width=1098&amp;amp;height=892&amp;amp;face=0_0_1098_892,https://scrap.kakaocdn.net/dn/eeHGU3/hyTMdR4JIr/CeCkzL0mK5B8vRNQdsdPfk/img.png?width=1106&amp;amp;height=842&amp;amp;face=0_0_1106_842&quot;&gt;&lt;a href=&quot;https://ratsgo.github.io/nlpbook/docs/language_model/tr_self_attention/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://ratsgo.github.io/nlpbook/docs/language_model/tr_self_attention/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/qaa9e/hyTMfWBWK0/1cIKDf0K2HqzBkxnvC5LKK/img.png?width=2474&amp;amp;height=1370&amp;amp;face=0_0_2474_1370,https://scrap.kakaocdn.net/dn/xb08U/hyTMcFBKzU/V4amR3QbPvQMoEPKsOA9p0/img.png?width=1098&amp;amp;height=892&amp;amp;face=0_0_1098_892,https://scrap.kakaocdn.net/dn/eeHGU3/hyTMdR4JIr/CeCkzL0mK5B8vRNQdsdPfk/img.png?width=1106&amp;amp;height=842&amp;amp;face=0_0_1106_842');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Self Attention&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;pratical tips for Natural Language Processing&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;ratsgo.github.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>머신러닝</category>
      <category>Attention</category>
      <category>Bert</category>
      <category>Transformer</category>
      <category>버트</category>
      <category>셀프어텐션</category>
      <category>어텐션</category>
      <category>트랜스포머</category>
      <author>여성게</author>
      <guid isPermaLink="true">https://coding-start.tistory.com/425</guid>
      <comments>https://coding-start.tistory.com/425#entry425comment</comments>
      <pubDate>Wed, 30 Aug 2023 17:39:48 +0900</pubDate>
    </item>
    <item>
      <title>[HTTP, HTTPS] x-forwarded-for(XFF) header</title>
      <link>https://coding-start.tistory.com/423</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://umbum.dev/1221&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://umbum.dev/1221&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1680849925855&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Client IP 구하기 : X-Forwarded-For와 X-Real-IP&quot; data-og-description=&quot;Proxy / VPN X-Real-IP는 바로 직전 client의 IP를 나타낸다. &amp;#96;&amp;#96;&amp;#96; proxy_set_header X-Real-IP $remote_addr; 설정에서 $remote_addr이, nginx가 수신한 client IP를 의미한다. User - Nginx - Tomcat 일 때, X-Real-IP는 User IP 가 된다. Us&quot; data-og-host=&quot;umbum.dev&quot; data-og-source-url=&quot;https://umbum.dev/1221&quot; data-og-url=&quot;https://umbum.dev/1221&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/kh6Th/hyScuUPll2/tnD5Xj8n0st7174Tue9YUK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bgkMhT/hyScFINOuM/BmNqkntJL8soI0VqxzARj1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800&quot;&gt;&lt;a href=&quot;https://umbum.dev/1221&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://umbum.dev/1221&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/kh6Th/hyScuUPll2/tnD5Xj8n0st7174Tue9YUK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bgkMhT/hyScFINOuM/BmNqkntJL8soI0VqxzARj1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Client IP 구하기 : X-Forwarded-For와 X-Real-IP&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Proxy / VPN X-Real-IP는 바로 직전 client의 IP를 나타낸다. ``` proxy_set_header X-Real-IP $remote_addr; 설정에서 $remote_addr이, nginx가 수신한 client IP를 의미한다. User - Nginx - Tomcat 일 때, X-Real-IP는 User IP 가 된다. Us&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;umbum.dev&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>인프라/네트워크(기초)</category>
      <author>여성게</author>
      <guid isPermaLink="true">https://coding-start.tistory.com/423</guid>
      <comments>https://coding-start.tistory.com/423#entry423comment</comments>
      <pubDate>Fri, 7 Apr 2023 15:45:43 +0900</pubDate>
    </item>
    <item>
      <title>Spring cloud stream kafka - concurrency</title>
      <link>https://coding-start.tistory.com/420</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;238&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rVk3r/btrIiJQdUzz/QpVkWJzpR3UkkuPkRamyK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rVk3r/btrIiJQdUzz/QpVkWJzpR3UkkuPkRamyK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rVk3r/btrIiJQdUzz/QpVkWJzpR3UkkuPkRamyK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrVk3r%2FbtrIiJQdUzz%2FQpVkWJzpR3UkkuPkRamyK0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;800&quot; height=&quot;238&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;238&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;spring.cloud.stream.bindings.&amp;lt;channelName&amp;gt;.consumer.concurrency 옵션과 관련해 설명한다.&lt;/p&gt;
&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;토픽 이름 : A-topic&lt;/li&gt;
&lt;li&gt;파티션 개수 : 4&lt;/li&gt;
&lt;li&gt;앱 인스턴스 개수 : 1(concurrency == 2)&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;보통은 위와 같이 설정을 하게 되면, 당연히 하나의 앱에서 2개는 동시 처리 하겠구나 생각을 하기 마련이지만 실제로는 그렇게 동작하지 않을 수 있다. 아래와 같이 컨슈머 그룹이 구성 되어 있다고 생각해보자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1658906094707&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;./kafka-consumer-groups.sh --bootstrap-server localhost:9092 --group a-topic-group --describe

GROUP         TOPIC       PARTITION  CURRENT-OFFSET  LOG-END-OFFSET  LAG             CONSUMER-ID                                             CLIENT-ID
a-topic-group a-topic     2          18846           18846           0               consumer-a-topic-3-cdb73066-d0de-4079-8fb0-a5663ba9c76d consumer-a-topic-3
a-topic-group a-topic     3          18887           18887           0               consumer-a-topic-3-cdb73066-d0de-4079-8fb0-a5663ba9c76d consumer-a-topic-3
a-topic-group a-topic     0          18838           18838           0               consumer-a-topic-2-2f6ac6eb-35f1-464a-b89e-0ea0579c28b2 consumer-a-topic-2
a-topic-group a-topic     1          19020           19020           0               consumer-a-topic-2-2f6ac6eb-35f1-464a-b89e-0ea0579c28b2 consumer-a-topic-2&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개의 컨슈머(CONSUMER-ID를 기준으로 보자)가 각각 2개의 파티션을 나눠가지고 있는데, 여기서 카프카의 특징을 보아야 한다. &quot;하나의 컨슈머는 요청 하나씩만 처리가능하다&quot; 이 특징에서 우리가 기대한대로 동작이 안하는것인데, 이유는 메시지가 2개가 발행되었고 이 2개의 메시지가 위 파티션중에 0,1번에 들어갔다고 보자. 그러면 우리는 2개가 동시에 처리 될것이라 기대하지만 실제로 컨슈머가 한놈이기때문에 먼저 들어온 것을 처리하고 다음것을 처리한다.(컨슈머 하나는 반드시 한번에 하나의 요청만 처리) 그렇기에 2개가 동시에 실행되지 않는 것이고 만약에 운좋게 서로 다른 컨슈머가 붙은 파티션으로 메시지가 들어가면 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;그렇다면 우리가 동시에 처리하고 싶다면? &quot;concurrency == 파티션 개수&quot;로 설정해주면 된다. 그러면 아래와 같이 하나의 앱에 4개의 컨슈머가 각각 다른 파티션에 할당될 것이고 실제로 4개의 메시지가 동시에 처리 될 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1658906337067&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;./kafka-consumer-groups.sh --bootstrap-server localhost:9092 --group a-topic-group --describe

GROUP         TOPIC       PARTITION  CURRENT-OFFSET  LOG-END-OFFSET  LAG             CONSUMER-ID                                             CLIENT-ID
a-topic-group a-topic     2          18846           18846           0               consumer-a-topic-3-cdb73066-d0de-4079-8fb0-a5663ba9c76d consumer-a-topic-3
a-topic-group a-topic     3          18887           18887           0               consumer-a-topic-3-dfdfad01-d0de-4079-8fb0-a5663ba9c76d consumer-a-topic-3
a-topic-group a-topic     0          18838           18838           0               consumer-a-topic-2-dcvzc023-35f1-464a-b89e-0ea0579c28b2 consumer-a-topic-2
a-topic-group a-topic     1          19020           19020           0               consumer-a-topic-2-2f6ac6eb-35f1-464a-b89e-0ea0579c28b2 consumer-a-topic-2&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Middleware/Kafka&amp;amp;RabbitMQ</category>
      <category>Concurrency</category>
      <category>Kafka</category>
      <category>Spring</category>
      <category>Spring cloud</category>
      <category>컨슈머</category>
      <category>파티션</category>
      <author>여성게</author>
      <guid isPermaLink="true">https://coding-start.tistory.com/420</guid>
      <comments>https://coding-start.tistory.com/420#entry420comment</comments>
      <pubDate>Wed, 27 Jul 2022 16:19:19 +0900</pubDate>
    </item>
    <item>
      <title>딥러닝 - BERT(Bidirectional Encoder Representations from Transformers)</title>
      <link>https://coding-start.tistory.com/416</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-02-17 오전 10.31.55.png&quot; data-origin-width=&quot;642&quot; data-origin-height=&quot;350&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cAT39Q/btrtsUZI8Y4/t3PlfBKYNJkJx4lu5ofnMK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cAT39Q/btrtsUZI8Y4/t3PlfBKYNJkJx4lu5ofnMK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cAT39Q/btrtsUZI8Y4/t3PlfBKYNJkJx4lu5ofnMK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcAT39Q%2FbtrtsUZI8Y4%2Ft3PlfBKYNJkJx4lu5ofnMK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;642&quot; height=&quot;350&quot; data-filename=&quot;스크린샷 2022-02-17 오전 10.31.55.png&quot; data-origin-width=&quot;642&quot; data-origin-height=&quot;350&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BERT는 트랜스포머를 이용하여 구현되었으며, 위키피디아와 BooksCorpus와 같은 레이블이 없는 텍스트 데이터로 사전 훈련된 언어 모델이다. BERT는 이미 기 학습된 사전 훈련 모델에 레이블이 있는 레이어 층을 하나 쌓아서 훈련해 파이미터를 재조정하여 다른 작업(task)에서도 좋은 성능을 낼 수 있다. 이러한 다른 작업을 위해 레이어를 쌓은 후 훈련하여 파라미터를 재 조정하는 과정을 파인튜닝(fine-tuning)이라고한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-02-17 오전 10.35.14.png&quot; data-origin-width=&quot;590&quot; data-origin-height=&quot;212&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bEYoX2/btrtsGmpAdT/qysu6YjomezbyP1zZMeXkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bEYoX2/btrtsGmpAdT/qysu6YjomezbyP1zZMeXkK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bEYoX2/btrtsGmpAdT/qysu6YjomezbyP1zZMeXkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbEYoX2%2FbtrtsGmpAdT%2Fqysu6YjomezbyP1zZMeXkK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;590&quot; height=&quot;212&quot; data-filename=&quot;스크린샷 2022-02-17 오전 10.35.14.png&quot; data-origin-width=&quot;590&quot; data-origin-height=&quot;212&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1645079338876&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;언어 모델(Language Model, LM)
언어 모델이란 단어들의 시퀀스에 대한 확률 분포다. 간단히 말하면 단어들의 모음이 있을 때 해당 단어의 모임이 
어떤 확률로 등장할지를 나태나는 값이라 생각하면 된다. 예를 들면, Word2vec 모델 중 CBOW 모델은 주변 
단어들을 통한 중앙 단어 예측이 학습의 목적이다. 즉, 문장 시퀀스 중 t번째 단어를 예측하기 위해 해당 단어의
앞,뒤 c개의 단어를 사용하는데, 앞뒤로 총 2c개의 단어 모음이 있을 때 t번째 위치에 올 단어에 대한 확률 분포를
찾는 언어 모델이고, 언어 모델의 성능은 이 확률을 최대화 하는 것이다.&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;BERT의 크기&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-02-17 오전 10.35.45.png&quot; data-origin-width=&quot;554&quot; data-origin-height=&quot;220&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EtPtl/btrtt5Nt7Nd/mXJQACetT9PeqeEUdGS55k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EtPtl/btrtt5Nt7Nd/mXJQACetT9PeqeEUdGS55k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EtPtl/btrtt5Nt7Nd/mXJQACetT9PeqeEUdGS55k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEtPtl%2Fbtrtt5Nt7Nd%2FmXJQACetT9PeqeEUdGS55k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;554&quot; height=&quot;220&quot; data-filename=&quot;스크린샷 2022-02-17 오전 10.35.45.png&quot; data-origin-width=&quot;554&quot; data-origin-height=&quot;220&quot;/&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;버트는 트랜스포머의 인코더-디코더의 구조를 따라가는 것은 아니고, 인코더만 여러 레이어로 쌓아 올린 구조이다. Base 버전에서는 총 12개, Large 버전에서는 총 24개의 인코더 레이어를 쌓았다. 그리고 각 버전은 모델의 차원수도 차이가 있고, 셀프어텐션 헤드수도 다르기때문에 아래와 같은 파라미터 개수를 갖는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1645061837456&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;BERT-Base : L=12, D=768, A=12 : 110M개의 파라미터
BERT-Large : L=24, D=1024, A=16 : 340M개의 파라미터&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;BERT의 문맥을 반영한 임베딩(Contextual Embedding)&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-02-17 오전 10.38.20.png&quot; data-origin-width=&quot;311&quot; data-origin-height=&quot;178&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YDKvk/btrtvsVYW84/NWiO0s0biHIWxX9lNWD0yk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YDKvk/btrtvsVYW84/NWiO0s0biHIWxX9lNWD0yk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YDKvk/btrtvsVYW84/NWiO0s0biHIWxX9lNWD0yk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYDKvk%2FbtrtvsVYW84%2FNWiO0s0biHIWxX9lNWD0yk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;311&quot; height=&quot;178&quot; data-filename=&quot;스크린샷 2022-02-17 오전 10.38.20.png&quot; data-origin-width=&quot;311&quot; data-origin-height=&quot;178&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BERT의 입력은 임베딩 층(Embedding Layer)를 지난 임베딩 벡터들이다. BERT base 기준 d_model을 768로 정의하였기 때문에 문장의 시퀀스들의 각각의 입력 차원은 768차원이다. 각 입력들은 총 12개의 레이어를 지나면서 연산된 후, 동일하게 각 단어에 대해서 768차원의 벡터를 출력하는데, 각 출력들은 모두 문맥을 고려한 벡터가 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-02-17 오전 10.40.48.png&quot; data-origin-width=&quot;587&quot; data-origin-height=&quot;178&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHsx7q/btrtxlaJiR4/pUl52yMKw64p2dprS5xbl0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHsx7q/btrtxlaJiR4/pUl52yMKw64p2dprS5xbl0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHsx7q/btrtxlaJiR4/pUl52yMKw64p2dprS5xbl0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHsx7q%2FbtrtxlaJiR4%2FpUl52yMKw64p2dprS5xbl0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;587&quot; height=&quot;178&quot; data-filename=&quot;스크린샷 2022-02-17 오전 10.40.48.png&quot; data-origin-width=&quot;587&quot; data-origin-height=&quot;178&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 그림에서 [CLS] 토큰의 경우 초기 입력은 단순 임베딩 층을 지난 벡터에 지나지 않지만, 총 12개의 레이어를 지난 후 출력으로 나온 CLS의 벡터는 입력으로 들어간 모든 토큰을 참고한 문맥 정보를 가진 벡터가 된다. 이러한 연산은 CLS 토큰 이외의 모든 토큰도 마찬가지가 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-02-17 오전 10.42.10.png&quot; data-origin-width=&quot;342&quot; data-origin-height=&quot;118&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bTZUaI/btrtxlhxA5e/vohOK1TzEEwXy9vfD2SUQK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bTZUaI/btrtxlhxA5e/vohOK1TzEEwXy9vfD2SUQK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bTZUaI/btrtxlhxA5e/vohOK1TzEEwXy9vfD2SUQK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbTZUaI%2FbtrtxlhxA5e%2FvohOK1TzEEwXy9vfD2SUQK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;342&quot; height=&quot;118&quot; data-filename=&quot;스크린샷 2022-02-17 오전 10.42.10.png&quot; data-origin-width=&quot;342&quot; data-origin-height=&quot;118&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 그림을 봤을 때 모든 토큰은 모든 토큰들을 참고하고 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-02-17 오전 10.42.50.png&quot; data-origin-width=&quot;636&quot; data-origin-height=&quot;257&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ck7aLX/btrtsUenepO/Iw79gwLpnegTRf0XzT9iJk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ck7aLX/btrtsUenepO/Iw79gwLpnegTRf0XzT9iJk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ck7aLX/btrtsUenepO/Iw79gwLpnegTRf0XzT9iJk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fck7aLX%2FbtrtsUenepO%2FIw79gwLpnegTRf0XzT9iJk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;257&quot; data-filename=&quot;스크린샷 2022-02-17 오전 10.42.50.png&quot; data-origin-width=&quot;636&quot; data-origin-height=&quot;257&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 그림은 각 입력이 총 12개의 레이어를 지나는 모습과, 각 레이어의 세부 구성을 보여준다. 결론적으로는 각 토큰들은 12레이어를 지나면서 셀프 어텐션을 통해 문맥 정보를 담게 된다.&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;BERT의 서브워드 토크나이저(WordPiece)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BERT는 단어보다 더 작은 단위로 쪼개는 서브워드 토크나이저를 사용한다. 서브워드 토크나이저는 기본적으로 자주 등장하는 단어는 그대로 단어 집합에 추가하지만, 자주 등장하지 않는 단어의 경우에는 더 작은 단위인 서브워드로 분리되어 서브워드들이 단어 집합에 추가된다는 아이디어를 갖는다. 만약 일반적인 토크나이저라면 존재하지 않는 단어일 경우 OOV 문제가 발생하지만, 서브워드 토크나이저의 경우 해당 단어가 단어집합에 존재하지 않는다고 해서, 서브워드 또한 존재하지 않는다는 의미가 아니므로 해당 단어를 더 쪼개려고 시도한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1645062526019&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;print(tokenizer.vocab['embeddings'])
-&amp;gt; em, ##bed, ##ding, ##s&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;이렇게 서브워드 토크나이저를 이용하여 문장 시퀀스들에 대해 WordPiece Embedding(정수 인코딩)을 진행한다.&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;포지션 임베딩(Position Embedding)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트랜스포머 모델에서는 포지셔널 인코딩이라는 방법으로 단어의 위치를 표현했는데, 사인함수와 코사인 함수를 이용해 각 토큰 행렬에 위치정보를 더했다. BERT는 유사하지만 사인,코사인 함수를 이용하지는 않고, 학습을 통해 얻는 포지션 임베딩을 사용한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-02-17 오전 10.52.35.png&quot; data-origin-width=&quot;378&quot; data-origin-height=&quot;172&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/buijvv/btrtxlu5z5v/uiLb5p6NUKvTzOs6Imb0N0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/buijvv/btrtxlu5z5v/uiLb5p6NUKvTzOs6Imb0N0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/buijvv/btrtxlu5z5v/uiLb5p6NUKvTzOs6Imb0N0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbuijvv%2Fbtrtxlu5z5v%2FuiLb5p6NUKvTzOs6Imb0N0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;378&quot; height=&quot;172&quot; data-filename=&quot;스크린샷 2022-02-17 오전 10.52.35.png&quot; data-origin-width=&quot;378&quot; data-origin-height=&quot;172&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 그림에서 보이듯 워드피스 임베딩을 통해 나온 벡터에 포지션 임베딩을 더해주는 행위를 하여 임베딩에 위치정보를 반영한다. 여기서 포지션 임베딩은 위치 정보를 위한 임베딩 레이어를 하나 더 사용하고, 만약 문장의 길이가 4라면 4개의 포지션 임베딩 벡터를 학습시킨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1645062833847&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;첫번째 단어의 임베딩 벡터 + 0번 포지션 임베딩 벡터
두번째 단어의 임베딩 벡터 + 1번 포지션 임베딩 벡터
세번째 단어의 임베딩 벡터 + 2번 포지션 임베딩 벡터
네번째 단어의 임베딩 벡터 + 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;실제 BERT에서는 문장의 최대 길이를 512로 하고 있으므로 총 512개의 포지션 임베딩 벡터가 학습된다. 지금까지 총 2개의 임베딩 레이어가 나왔는데 버트에서는 추가적으로 세그먼트 임베딩이라는 1개의 임베딩 층을 더 사용한다.&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;BERT의 pre-trained : 마스크드 언어 모델(Masked Language Model, MLM)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BERT는 사전 훈련을 위해서 입력으로 들어가는 입력 텍스트의 15%의 단어를 랜덤으로 마스킹한다. 그리고 인공 신경망에게 이 가려진 단어들을 예측하도록 한다. 더 정확하게는 15% 단어 전부를 마스킹 하지 않고 아래와 같은 규칙을 따른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1645063321303&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;80%의 단어들은 [MASK]로 변경한다.
Ex) The man went to the store &amp;rarr; The man went to the [MASK]

10%의 단어들은 랜덤으로 단어가 변경된다.
Ex) The man went to the store &amp;rarr; The man went to the dog

10%의 단어들은 동일하게 둔다.
Ex) The man went to the store &amp;rarr; The man went to the store&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;전체 단어의 85%는 학습에 사용되지 않는다. 마스크드 언어 모델의 학습에 사용되는 단어는 전체 단어의 15%이다. 학습에 사용되는 12%는 마스킹된 후 원래 단어를 예측하는 방식, 1.5%는 랜덤으로 변경된 단어의 원래 단어를 예측하는 방식, 나머지 1.5%는 단어가 변경되지 않았지만 BERT는 이 단어가 원래의 단어인지 변경된 단어인지 모르기에 원래단어가 무엇이었는지 예측해본다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-02-17 오전 11.06.56.png&quot; data-origin-width=&quot;454&quot; data-origin-height=&quot;232&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqwRdI/btrtxlaKe1P/OhxUuBvgM8iUqskxaeRvpK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqwRdI/btrtxlaKe1P/OhxUuBvgM8iUqskxaeRvpK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqwRdI/btrtxlaKe1P/OhxUuBvgM8iUqskxaeRvpK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbqwRdI%2FbtrtxlaKe1P%2FOhxUuBvgM8iUqskxaeRvpK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;454&quot; height=&quot;232&quot; data-filename=&quot;스크린샷 2022-02-17 오전 11.06.56.png&quot; data-origin-width=&quot;454&quot; data-origin-height=&quot;232&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1645063633391&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;'dog' 토큰은 [MASK]로 변경되었다.
'he'는 랜덤 단어 'king'으로 변경되었다.
'play'는 변경되진 않았지만 예측에 사용된다.&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;BERT의 pre-trained :&lt;span&gt; 다음 문장 예측(Next Sentence Prediction, NSP)&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;BERT는 두 개의 문장을 준 후에 이 문장이 이어지는 문장인지 아닌지를 맞추는 방식으로 사전 학습한다.&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_1645063661762&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;이어지는 문장의 경우
Sentence A : The man went to the store.
Sentence B : He bought a gallon of milk.
Label = IsNextSentence

이어지는 문장이 아닌 경우 경우
Sentence A : The man went to the store.
Sentence B : dogs are so cute.
Label = NotNextSentence&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-02-17 오전 11.07.52.png&quot; data-origin-width=&quot;473&quot; data-origin-height=&quot;232&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HKICq/btrtAPhP71T/dy0MLwsozRH3Fc4v4m4R4k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HKICq/btrtAPhP71T/dy0MLwsozRH3Fc4v4m4R4k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HKICq/btrtAPhP71T/dy0MLwsozRH3Fc4v4m4R4k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHKICq%2FbtrtAPhP71T%2Fdy0MLwsozRH3Fc4v4m4R4k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;473&quot; height=&quot;232&quot; data-filename=&quot;스크린샷 2022-02-17 오전 11.07.52.png&quot; data-origin-width=&quot;473&quot; data-origin-height=&quot;232&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BERT의 입력으로 넣을 때에는 [SEP]라는 특별 토큰을 사용해 두 문장을 구분한다. 그리고 위 그림에서 보듯이 문장이 이어지는지 아닌지 등의 분류 문제에는 CLS 토큰의 출력을 이용한다. 또한 마스크드 언어 모델 학습과 다음 문장 예측은 서로 별도로 학습하는 것이 아니라, 각 문제의 loss를 합하여 학습이 동시에 이루어진다.&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;세그먼트 임베딩(Segment Embedding)&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-02-17 오전 11.10.10.png&quot; data-origin-width=&quot;515&quot; data-origin-height=&quot;256&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AeX3D/btrtrYnQrG2/dqOJlrOWCksBAFxWrF3ARk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AeX3D/btrtrYnQrG2/dqOJlrOWCksBAFxWrF3ARk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AeX3D/btrtrYnQrG2/dqOJlrOWCksBAFxWrF3ARk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAeX3D%2FbtrtrYnQrG2%2FdqOJlrOWCksBAFxWrF3ARk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;515&quot; height=&quot;256&quot; data-filename=&quot;스크린샷 2022-02-17 오전 11.10.10.png&quot; data-origin-width=&quot;515&quot; data-origin-height=&quot;256&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BERT는 QA등과 같은 두 개의 문장 입력이 필요한 태스크를 풀기도 하는데, 문장 구분을 위해서 세그먼트 임베딩이라는 또 다른 임베딩 층을 사용한다. 첫번째 문장에는 Sentence0 임베딩, 두번째 문장에는 Sentence1 임베딩을 더해주는 방식이며 임베딩 벡터는 두개만 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론적으로 BERT는 총 3개의 임베딩 층이 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1645063898386&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;WordPiece Embedding : 실질적인 입력이 되는 워드 임베딩. 임베딩 벡터의 종류는 단어 집합의 크기로 30,522개.
Position Embedding : 위치 정보를 학습하기 위한 임베딩. 임베딩 벡터의 종류는 문장의 최대 길이인 512개.
Segment Embedding : 두 개의 문장을 구분하기 위한 임베딩. 임베딩 벡터의 종류는 문장의 최대 개수인 2개.&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;만약 BERT가 두 개의 문장을 입력받을 필요가 없는 태스크인 경우 세그먼트 임베딩을 전체 입력에 대해 Sentence 0 임베딩만 더해준다.&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;BERT를 파인 튜닝(fine-tunng) 하기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;i&gt;1)하나의 텍스트에 대한 텍스트 분류 유형(Single Text Claasification)&lt;/i&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-02-17 오후 1.42.31.png&quot; data-origin-width=&quot;362&quot; data-origin-height=&quot;273&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qrs6Z/btrtxqXsRpj/A0fMgfqikEQkxmFLuNbhRK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qrs6Z/btrtxqXsRpj/A0fMgfqikEQkxmFLuNbhRK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qrs6Z/btrtxqXsRpj/A0fMgfqikEQkxmFLuNbhRK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fqrs6Z%2FbtrtxqXsRpj%2FA0fMgfqikEQkxmFLuNbhRK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;362&quot; height=&quot;273&quot; data-filename=&quot;스크린샷 2022-02-17 오후 1.42.31.png&quot; data-origin-width=&quot;362&quot; data-origin-height=&quot;273&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입력이 하나의 문장이고, 해당 텍스트에 대한 분류를 할때는 앞서 설명한 것과 같이 CLS 토큰의 출력을 이용한다. 즉, 파인 튜닝 단계에서 텍스트 분류 문제를 풀기 위해서 CLS 토큰 출력 위치에 Dense Layer(fully-connected layer) 층을 추가하여 분류에 대한 예측을 하게 된다.&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;i&gt;2)하나의 텍스트에 대한 태깅 작업(Tagging)&lt;/i&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-02-17 오후 1.46.10.png&quot; data-origin-width=&quot;362&quot; data-origin-height=&quot;273&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YYxB1/btrtydqlNWN/WIYbzQiDaAKkGW5rtdOBM0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YYxB1/btrtydqlNWN/WIYbzQiDaAKkGW5rtdOBM0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YYxB1/btrtydqlNWN/WIYbzQiDaAKkGW5rtdOBM0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYYxB1%2FbtrtydqlNWN%2FWIYbzQiDaAKkGW5rtdOBM0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;362&quot; height=&quot;273&quot; data-filename=&quot;스크린샷 2022-02-17 오후 1.46.10.png&quot; data-origin-width=&quot;362&quot; data-origin-height=&quot;273&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 텍스트에 대해 엔티티 혹은 품사를 태깅하는 작업에 대해 파인 튜닝 할때는 스페셜 토큰을 제외한 실제 입력 토큰의 출력층에 Dense Layer를 사용하여 분류에 대한 예측을 하게 된다.&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;i&gt;3)하나의 텍스트에 대한 태깅 작업(Tagging)&lt;/i&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-02-17 오후 1.47.28.png&quot; data-origin-width=&quot;557&quot; data-origin-height=&quot;273&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cKueBb/btrtBNYLjvR/5UE5lpLlPRERNPpuc4nWIk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cKueBb/btrtBNYLjvR/5UE5lpLlPRERNPpuc4nWIk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cKueBb/btrtBNYLjvR/5UE5lpLlPRERNPpuc4nWIk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcKueBb%2FbtrtBNYLjvR%2F5UE5lpLlPRERNPpuc4nWIk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;557&quot; height=&quot;273&quot; data-filename=&quot;스크린샷 2022-02-17 오후 1.47.28.png&quot; data-origin-width=&quot;557&quot; data-origin-height=&quot;273&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;텍스트의 쌍을 입력으로 받는 대표적인 태스크로 자연어 추론(Natural language inference)이 있는데,&amp;nbsp; 두 문장이 입력으로 들어오고 두문장이 논리적으로 어떤 관계에 있는지를 분류하는 것이다. 유형으로는 모순 관계(contradiction), 함의 관계(entailment), 중립 관계(neutral)이 있다. 해당 문제의 입력은 두 개의 문장이므로 SEP라는 스페셜 토큰으로 문장을 구분한다. 그리고 해당 문제는 분류 문제에 속하므로, CLS 토큰의 출력층에 Dense Layer를 쌓아서 분류를 예측한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 설명한 문제 이외로도 하나의 문장과 하나의 문단을 넣어서 첫번째 문장(질문)에 해당하는 답을 문단에서 뽑아내는 질의 응답에 대한 문제등도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;어텐션 마스크(Attention Mask)&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-02-17 오후 1.53.49.png&quot; data-origin-width=&quot;466&quot; data-origin-height=&quot;234&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/G5gNY/btrtt3JhLJt/PC5NxMaaC7PAjkPYkZTxAk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/G5gNY/btrtt3JhLJt/PC5NxMaaC7PAjkPYkZTxAk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/G5gNY/btrtt3JhLJt/PC5NxMaaC7PAjkPYkZTxAk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FG5gNY%2Fbtrtt3JhLJt%2FPC5NxMaaC7PAjkPYkZTxAk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;466&quot; height=&quot;234&quot; data-filename=&quot;스크린샷 2022-02-17 오후 1.53.49.png&quot; data-origin-width=&quot;466&quot; data-origin-height=&quot;234&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BERT는 입력으로 어텐션 마스크라는 시퀀스 입력이 추가로 필요하다. 쉽게 말해 어텐션 마스크는 연산이 필요없는 패딩 토큰에 대해 어텐션을 하지 않도록 마스킹해주는 역할이다. 이 값은 0과 1값으로 가지는데, 실제로 입력 시퀀스중 [PAD] 토큰의 위치에 해당하는 어텐션 마스크는 0으로, 실제 연산에 필요한 토큰 위치는 1로 채워지게 된다.&lt;/p&gt;</description>
      <category>머신러닝</category>
      <category>Bert</category>
      <category>fine-tuning</category>
      <category>버트</category>
      <category>셀프 어텐션</category>
      <category>어텐션</category>
      <category>어텐션 마스크</category>
      <category>파인튜닝</category>
      <author>여성게</author>
      <guid isPermaLink="true">https://coding-start.tistory.com/416</guid>
      <comments>https://coding-start.tistory.com/416#entry416comment</comments>
      <pubDate>Thu, 17 Feb 2022 11:13:27 +0900</pubDate>
    </item>
    <item>
      <title>딥러닝 - 트랜스포머(Transformer)</title>
      <link>https://coding-start.tistory.com/415</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;트랜스포머(Transformer)는 구글이 발표한 논문인 &quot;Attention is all you need&quot;에서 나온 모델로 기존의 seq2seq의 구조인 인코더-디코더를 따르지만, 내부적으로 RNN 레이어 없이, 어텐션(Attention)으로만 구현한 모델이다.&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;RNN을 이용한 기존 seq2seq의 한계&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존의 seq2seq 모델은 인코더-디코더 구조로 구성되어있고, 여기서 인코더는 입력 시퀀스를 하나의 벡터 표현으로 압축하고, 디코더는 이 벡터 표현을 통해 출력 시퀀스를 만들어냈다. 하지만 이러한 구조는 인코더가 입력 시퀀스를 하나의 벡터로 압축하는 과정에서 입력 시퀀스의 정보가 일부 손실되는 단점이 있었다. 또한 RNN의 고질적인 기울기 소실 문제까지 더해졌다.&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;트랜스포머(Transformer)의 주요 하이퍼파라미터&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-02-15 오후 7.02.15.png&quot; data-origin-width=&quot;855&quot; data-origin-height=&quot;364&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Oi8D0/btrtrp4VYWA/2BH5zmCsB9FVuxDRVJkpt1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Oi8D0/btrtrp4VYWA/2BH5zmCsB9FVuxDRVJkpt1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Oi8D0/btrtrp4VYWA/2BH5zmCsB9FVuxDRVJkpt1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOi8D0%2Fbtrtrp4VYWA%2F2BH5zmCsB9FVuxDRVJkpt1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;855&quot; height=&quot;364&quot; data-filename=&quot;스크린샷 2022-02-15 오후 7.02.15.png&quot; data-origin-width=&quot;855&quot; data-origin-height=&quot;364&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;트랜스포머(Transformer)&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-02-15 오후 7.03.37.png&quot; data-origin-width=&quot;296&quot; data-origin-height=&quot;176&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lCnFZ/btrtqMMOXpb/1vfZLJmI8DGGECFZuLNDE1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lCnFZ/btrtqMMOXpb/1vfZLJmI8DGGECFZuLNDE1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lCnFZ/btrtqMMOXpb/1vfZLJmI8DGGECFZuLNDE1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlCnFZ%2FbtrtqMMOXpb%2F1vfZLJmI8DGGECFZuLNDE1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;296&quot; height=&quot;176&quot; data-filename=&quot;스크린샷 2022-02-15 오후 7.03.37.png&quot; data-origin-width=&quot;296&quot; data-origin-height=&quot;176&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트랜스포머는 RNN을 사용하지 않지만 기존 seq2seq처럼 인코더-디코더 구조를 유지하고 있다. 이전 seq2seq 구조에서는 인코더와 디코더에서 각각 하나의 RNN이 t개의 시점(time step)을 가지는 구조였다면, 트랜스포머는 인코더와 디코더가 각각 N개층으로 쌓여있는 구조이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-02-15 오후 7.05.32.png&quot; data-origin-width=&quot;296&quot; data-origin-height=&quot;339&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nCOr5/btrtmWCSkiX/MHJySL6xFD3mHUtunTTryK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nCOr5/btrtmWCSkiX/MHJySL6xFD3mHUtunTTryK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nCOr5/btrtmWCSkiX/MHJySL6xFD3mHUtunTTryK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnCOr5%2FbtrtmWCSkiX%2FMHJySL6xFD3mHUtunTTryK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;296&quot; height=&quot;339&quot; data-filename=&quot;스크린샷 2022-02-15 오후 7.05.32.png&quot; data-origin-width=&quot;296&quot; data-origin-height=&quot;339&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 그림은 각 인코더, 디코더가 6개의 층으로 쌓여있는 구조이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-02-15 오후 7.06.05.png&quot; data-origin-width=&quot;598&quot; data-origin-height=&quot;234&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/clltAW/btrtoA0DehA/o73t86hSHZtKpfDJVSHyO1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/clltAW/btrtoA0DehA/o73t86hSHZtKpfDJVSHyO1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/clltAW/btrtoA0DehA/o73t86hSHZtKpfDJVSHyO1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FclltAW%2FbtrtoA0DehA%2Fo73t86hSHZtKpfDJVSHyO1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;598&quot; height=&quot;234&quot; data-filename=&quot;스크린샷 2022-02-15 오후 7.06.05.png&quot; data-origin-width=&quot;598&quot; data-origin-height=&quot;234&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;큰 구조를 보면 기존의 seq2seq 구조와 비슷하다.&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;포지셔널 인코딩(Positional Encoding)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RNN이 자연어 처리에서 유용했던 이유는 단어의 위치에 따라 단어를 순차적으로 입력받아서 처리하는 RNN의 특성으로 인해 각 단어의 위치 정보를 가질 수 있다는 점에 있었다. 하지만 트랜스포머는 단어 입력을 순차적으로 받는 방식이 아니라, 단어의 위치 정보를 알려 줄 수 있는 방법이 필요했다. 그래서 트랜스포머는 단어의 위치 정보를 얻기 위해 각 단어의 임베딩 벡터에 위치 정보들을 더하여 모델의 입력으로 사용하는데 이를 포지셔널 인코딩이라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-02-15 오후 7.08.31.png&quot; data-origin-width=&quot;598&quot; data-origin-height=&quot;250&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cyIyUT/btrtsVvHmRB/6EcKWDfT8MFasdERvcknnK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cyIyUT/btrtsVvHmRB/6EcKWDfT8MFasdERvcknnK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cyIyUT/btrtsVvHmRB/6EcKWDfT8MFasdERvcknnK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcyIyUT%2FbtrtsVvHmRB%2F6EcKWDfT8MFasdERvcknnK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;598&quot; height=&quot;250&quot; data-filename=&quot;스크린샷 2022-02-15 오후 7.08.31.png&quot; data-origin-width=&quot;598&quot; data-origin-height=&quot;250&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 그림을 보면 각 인코더, 디코드 층에 입력이 들어가기전 포지셔널 인코딩 레이어가 있는 것이 특징이다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-02-15 오후 7.09.27.png&quot; data-origin-width=&quot;529&quot; data-origin-height=&quot;120&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3996J/btrtqCwTNx9/hv8ZKG0CIqFDh94rMmGf8K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3996J/btrtqCwTNx9/hv8ZKG0CIqFDh94rMmGf8K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3996J/btrtqCwTNx9/hv8ZKG0CIqFDh94rMmGf8K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3996J%2FbtrtqCwTNx9%2Fhv8ZKG0CIqFDh94rMmGf8K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;529&quot; height=&quot;120&quot; data-filename=&quot;스크린샷 2022-02-15 오후 7.09.27.png&quot; data-origin-width=&quot;529&quot; data-origin-height=&quot;120&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;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-02-15 오후 7.10.25.png&quot; data-origin-width=&quot;302&quot; data-origin-height=&quot;77&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/v3YaA/btrtqCcBe5e/bdXyXXaK7kgOobd9rtQp3K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/v3YaA/btrtqCcBe5e/bdXyXXaK7kgOobd9rtQp3K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/v3YaA/btrtqCcBe5e/bdXyXXaK7kgOobd9rtQp3K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fv3YaA%2FbtrtqCcBe5e%2FbdXyXXaK7kgOobd9rtQp3K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;302&quot; height=&quot;77&quot; data-filename=&quot;스크린샷 2022-02-15 오후 7.10.25.png&quot; data-origin-width=&quot;302&quot; data-origin-height=&quot;77&quot;/&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-02-15 오후 7.14.54.png&quot; data-origin-width=&quot;339&quot; data-origin-height=&quot;170&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c4WwUB/btrtfFIKXfu/haJwbqMJVrqYKgdvsEawdk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c4WwUB/btrtfFIKXfu/haJwbqMJVrqYKgdvsEawdk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c4WwUB/btrtfFIKXfu/haJwbqMJVrqYKgdvsEawdk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc4WwUB%2FbtrtfFIKXfu%2FhaJwbqMJVrqYKgdvsEawdk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;339&quot; height=&quot;170&quot; data-filename=&quot;스크린샷 2022-02-15 오후 7.14.54.png&quot; data-origin-width=&quot;339&quot; data-origin-height=&quot;170&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pos는 입력 문장에서의 임베딩 벡터의 위치를 나타내며, i는 임베딩 벡터 내의 차원의 인덱스를 의미한다. 위의 식에 따르면 임베딩 벡터 내의 각 차원의 인덱스가 짝수인 경우에는 사인함수, 홀수 인경우에는 코사인 함수를 이용한다. 위와 같이 포지셔널 인코딩 방법을 이용하면 순서정보가 보존되는데, 같은 단어라고 하더라도 문장 내의 위치가 다르면 트랜스포머의 입력으로 들어가는 임베딩 벡터의 값이 달라진다. 즉, 트랜스포머의 입력은 순서 정보가 고려된 임베딩 벡터가 된다.&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;/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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-02-15 오후 7.42.39.png&quot; data-origin-width=&quot;230&quot; data-origin-height=&quot;274&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xjBYz/btrtnEhvXPc/kKBsiBSbCeEJ4qJ9YeLZi0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xjBYz/btrtnEhvXPc/kKBsiBSbCeEJ4qJ9YeLZi0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xjBYz/btrtnEhvXPc/kKBsiBSbCeEJ4qJ9YeLZi0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxjBYz%2FbtrtnEhvXPc%2FkKBsiBSbCeEJ4qJ9YeLZi0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;230&quot; height=&quot;274&quot; data-filename=&quot;스크린샷 2022-02-15 오후 7.42.39.png&quot; data-origin-width=&quot;230&quot; data-origin-height=&quot;274&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;pre id=&quot;code_1644921794113&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;인코더의 셀프 어텐션 : Query = Key = Value
디코더의 마스크드 셀프 어텐션 : Query = Key = Value
디코더의 인코더-디코더 어텐션 : Query : 디코더 벡터 / Key = Value : 인코더 벡터&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-02-15 오후 7.43.31.png&quot; data-origin-width=&quot;594&quot; data-origin-height=&quot;390&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KNsTo/btrtsVick26/zSb7hYEhcwRHFGGQmzjr1K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KNsTo/btrtsVick26/zSb7hYEhcwRHFGGQmzjr1K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KNsTo/btrtsVick26/zSb7hYEhcwRHFGGQmzjr1K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKNsTo%2FbtrtsVick26%2FzSb7hYEhcwRHFGGQmzjr1K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;594&quot; height=&quot;390&quot; data-filename=&quot;스크린샷 2022-02-15 오후 7.43.31.png&quot; data-origin-width=&quot;594&quot; data-origin-height=&quot;390&quot;/&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;h4 data-ke-size=&quot;size20&quot;&gt;인코더&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-02-15 오후 7.44.55.png&quot; data-origin-width=&quot;277&quot; data-origin-height=&quot;293&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vndcE/btrtsUQ79IG/2DCKkNC5GBFmlvKDpwFnF1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vndcE/btrtsUQ79IG/2DCKkNC5GBFmlvKDpwFnF1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vndcE/btrtsUQ79IG/2DCKkNC5GBFmlvKDpwFnF1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvndcE%2FbtrtsUQ79IG%2F2DCKkNC5GBFmlvKDpwFnF1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;277&quot; height=&quot;293&quot; data-filename=&quot;스크린샷 2022-02-15 오후 7.44.55.png&quot; data-origin-width=&quot;277&quot; data-origin-height=&quot;293&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인코더를 하나의 층이라는 개념으로 보면, 하나의 인코더는 크게 총 2개의 셀프 어텐션과 피드 포워드 서브층으로 나뉜다.&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;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;셀프 어텐션이란 어텐션을 자기 자신에게 수행한다는 의미다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1644922336326&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Q : 입력 문장의 모든 단어 벡터들
K : 입력 문장의 모든 단어 벡터들
V : 입력 문장의 모든 단어 벡터들&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;셀프 어텐션은 입력 문장의 단어 벡터들을 가지고 수행한다고 했지만, 사실 셀프 어텐션은 인코더의 초기 입력인 dmodel의 차원을 가지는 단어 벡터들을 사용하여 셀프 어텐션을 수행하는 것이 아니라 우선 각 단어 벡터들로부터 Q벡터, K벡터, V벡터를 얻는 작업을 거친다. 이때 &amp;nbsp;Q벡터, K벡터, V벡터들은 초기 입력인 dmodel의 차원을 가지는 단어 벡터들보다 더 작은 차원을 가지는데 논문에서는 512의 차원을 가졌던 단어 벡터들을 64의 차원을 가지는 벡터로 변환하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;64라는 값은 트랜스포머의 num_heads 하이퍼파라미터로 결정되는데, 트랜스포머는 dmodel 차원을 num_heads로 나눈 값을 각 Q, K, V 벡터의 차원으로 결정한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-02-15 오후 7.56.38.png&quot; data-origin-width=&quot;290&quot; data-origin-height=&quot;321&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cQ9F4v/btrtpRuph7H/HCjQ4nzoiEI1BPvYGpYNWK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cQ9F4v/btrtpRuph7H/HCjQ4nzoiEI1BPvYGpYNWK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cQ9F4v/btrtpRuph7H/HCjQ4nzoiEI1BPvYGpYNWK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcQ9F4v%2FbtrtpRuph7H%2FHCjQ4nzoiEI1BPvYGpYNWK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;290&quot; height=&quot;321&quot; data-filename=&quot;스크린샷 2022-02-15 오후 7.56.38.png&quot; data-origin-width=&quot;290&quot; data-origin-height=&quot;321&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존의 벡터로부터 더 작은 벡터는 가중치 행렬을 곱하므로서 완성된다. 해당 가중치 행렬은 훈련 과정에서 학습된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;스케일드 닷-프로덕트 어텐션&lt;/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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-02-15 오후 8.00.42.png&quot; data-origin-width=&quot;488&quot; data-origin-height=&quot;332&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b558RU/btrtpPJ4LGC/FxKxLL3kRVn1YshkhjVeQK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b558RU/btrtpPJ4LGC/FxKxLL3kRVn1YshkhjVeQK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b558RU/btrtpPJ4LGC/FxKxLL3kRVn1YshkhjVeQK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb558RU%2FbtrtpPJ4LGC%2FFxKxLL3kRVn1YshkhjVeQK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;488&quot; height=&quot;332&quot; data-filename=&quot;스크린샷 2022-02-15 오후 8.00.42.png&quot; data-origin-width=&quot;488&quot; data-origin-height=&quot;332&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Q, K, V 벡터를 얻었다면 지금부터는 기존의 어텐션 메커니즘과 동일하다. 각 Q벡터는 모든 K벡터에 대해 어텐션 스코어를 구하고, 어텐션 분포를 구한 뒤에 이를 사용하여 모든 V벡터를 가중합하여 어텐션 값 또는 컨텍스트 벡터를 구한다. 이를 모든 Q벡터에 대해 반복한다. 스케일드 닷-프로덕트 어텐션은 이전 포스팅에서 다뤘던 어텐션 기법과는 다르게 어텐션 스코어를 구하기 위해 Q, V벡터의 내적만 하는 것이 아니라 아래와 같은 값을 스케일하는 수식이 하나더 들어간다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-02-16 오전 10.50.53.png&quot; data-origin-width=&quot;488&quot; data-origin-height=&quot;332&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XydDN/btrtvrORrFu/2V3PpGcHzV5RNtnk3cJTK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XydDN/btrtvrORrFu/2V3PpGcHzV5RNtnk3cJTK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XydDN/btrtvrORrFu/2V3PpGcHzV5RNtnk3cJTK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXydDN%2FbtrtvrORrFu%2F2V3PpGcHzV5RNtnk3cJTK0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;488&quot; height=&quot;332&quot; data-filename=&quot;스크린샷 2022-02-16 오전 10.50.53.png&quot; data-origin-width=&quot;488&quot; data-origin-height=&quot;332&quot;/&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;위 그림의 어텐션 스코어는 단어 I가 각 단어 I, am, a, student와 얼마나 연관이 있는지 보여주는 수치이다. 트랜스포머에서는 두 벡터의 내적값을 스케일링하는 값으로 K벡터의 차원(64)을 나타내는 dk에 루트를 씌우는 값을 이용했다. 여기서 dk는 8이라는 값을 가진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-02-16 오전 10.53.15.png&quot; data-origin-width=&quot;645&quot; data-origin-height=&quot;332&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Rsmdz/btrtmfvITGJ/Khtankj47iAWAcYAUopKv0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Rsmdz/btrtmfvITGJ/Khtankj47iAWAcYAUopKv0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Rsmdz/btrtmfvITGJ/Khtankj47iAWAcYAUopKv0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRsmdz%2FbtrtmfvITGJ%2FKhtankj47iAWAcYAUopKv0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;645&quot; height=&quot;332&quot; data-filename=&quot;스크린샷 2022-02-16 오전 10.53.15.png&quot; data-origin-width=&quot;645&quot; data-origin-height=&quot;332&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇게 나온 값을 소프트맥스 함수에 통과시켜 모든합이 1이되는 확률 분포를 만들고 이 확률(어텐션 가중치)를 모든 V벡터와 가중합하여 최종적인 어텐션 값을 구한다. 이 어텐션 값을 위 예제로 표현하면 단어 I에 대한 어텐션 값 또는 컨텍스트 벡터라고 할 수 있다. 이러한 과정을 모든 단어에 동일하게 반복한다. 하지만 내부적으로 따로 계산하지는 않고 모든 단어에대해 한번에 행렬 연산으로 모든 단어의 어텐션 값을 구하는 연산을 진행한다.&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;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-02-16 오전 10.55.40.png&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;318&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/be40E4/btrtpPKsMfg/fqCU0edJFQPO0PrMmuGpu0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/be40E4/btrtpPKsMfg/fqCU0edJFQPO0PrMmuGpu0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/be40E4/btrtpPKsMfg/fqCU0edJFQPO0PrMmuGpu0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbe40E4%2FbtrtpPKsMfg%2FfqCU0edJFQPO0PrMmuGpu0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;318&quot; data-filename=&quot;스크린샷 2022-02-16 오전 10.55.40.png&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;318&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 각 단어 벡터마다 일일히 가중치 행렬을 곱하는 것이 아니라, 문장 행렬에 가중치 행렬을 곱하여 Q, K, V 행렬을 구한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-02-16 오전 10.56.26.png&quot; data-origin-width=&quot;454&quot; data-origin-height=&quot;114&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LNf02/btrtqCxlDDa/BnAQn78iczWLN7oOQrDfb0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LNf02/btrtqCxlDDa/BnAQn78iczWLN7oOQrDfb0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LNf02/btrtqCxlDDa/BnAQn78iczWLN7oOQrDfb0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLNf02%2FbtrtqCxlDDa%2FBnAQn78iczWLN7oOQrDfb0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;454&quot; height=&quot;114&quot; data-filename=&quot;스크린샷 2022-02-16 오전 10.56.26.png&quot; data-origin-width=&quot;454&quot; data-origin-height=&quot;114&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇게 나온 Q행렬과 K행렬로는 어떻게 어텐션 스코어를 구할까? 위 그림과 같이 K행렬을 전치한 행렬과 Q행렬을 곱하고 스케일링 값을 나눠주면(K벡터의 루트를 씌운값) 각 단어의 어텐션스코어가 포함된 행렬이 나오게 된다. 예를 들어 I행과 student열의 값은 단어 I의 Q벡터와 student K벡터의 어텐션 스코어 값이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-02-16 오전 10.59.22.png&quot; data-origin-width=&quot;528&quot; data-origin-height=&quot;176&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cuew90/btrtmUZQ2wg/qfpgSZOH10BeMYUhAOgpI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cuew90/btrtmUZQ2wg/qfpgSZOH10BeMYUhAOgpI1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cuew90/btrtmUZQ2wg/qfpgSZOH10BeMYUhAOgpI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcuew90%2FbtrtmUZQ2wg%2FqfpgSZOH10BeMYUhAOgpI1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;528&quot; height=&quot;176&quot; data-filename=&quot;스크린샷 2022-02-16 오전 10.59.22.png&quot; data-origin-width=&quot;528&quot; data-origin-height=&quot;176&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 각 단어의 어텐션 값은 아래 수식대로 계산하면 나오게 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-02-16 오전 10.59.52.png&quot; data-origin-width=&quot;339&quot; data-origin-height=&quot;56&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbKlZ0/btrtlVdFmji/X8eqH8nVMFRi5w9HaVEZS0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbKlZ0/btrtlVdFmji/X8eqH8nVMFRi5w9HaVEZS0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbKlZ0/btrtlVdFmji/X8eqH8nVMFRi5w9HaVEZS0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbKlZ0%2FbtrtlVdFmji%2FX8eqH8nVMFRi5w9HaVEZS0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;339&quot; height=&quot;56&quot; data-filename=&quot;스크린샷 2022-02-16 오전 10.59.52.png&quot; data-origin-width=&quot;339&quot; data-origin-height=&quot;56&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 행렬 연산에 사용된 행렬의 크기를 모두 정리하자면, 우선 입력문장의 길이를 seq_len이라고 하면, 문장 행렬의 크기는 (seq_len, dmodel)이다. 여기에 3개의 가중치 행렬을 곱해서 Q, K, V 행렬을 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 행렬의 크기를 정의하기 위해 행렬의 각 행에 해당되는 Q벡터와 K벡터의 차원을 dk라고하고, V벡터의 차원을 dv라고 하자. 그렇다면 Q행렬과 K행렬의 크기는 (seq_len, dk)이며, V행렬의 크기는 (seq_len, dv)가 되어야한다. 그렇다면 문장 행렬과 Q, K, V 행렬의 크기로 가중치 행렬의 크기 추정이 가능하다. Wq, Wk는 (dmodel, dk)의 크기를 가지며, Wv는 (dmodel, dv)의 크기를 가진다. 단 논문에서는 dk와 dv의 차원은 dmodel/num_heads와 같다. 즉, dmodel/num_heads == dk == dv이다.&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;멀티 헤드 어텐션(Multi-head Attention)&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-02-16 오후 3.10.16.png&quot; data-origin-width=&quot;643&quot; data-origin-height=&quot;360&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nb2rS/btrtoAN7Rha/V0DpZ02XhWACDmlER5vZP1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nb2rS/btrtoAN7Rha/V0DpZ02XhWACDmlER5vZP1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nb2rS/btrtoAN7Rha/V0DpZ02XhWACDmlER5vZP1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fnb2rS%2FbtrtoAN7Rha%2FV0DpZ02XhWACDmlER5vZP1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;643&quot; height=&quot;360&quot; data-filename=&quot;스크린샷 2022-02-16 오후 3.10.16.png&quot; data-origin-width=&quot;643&quot; data-origin-height=&quot;360&quot;/&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;트랜스포머를 개발한 연구진은 한 번의 어텐션을 하는 것보다 여러번의 어텐션을 병렬로 사용하는 것이 더 효과적이라고 판단했다. 그래서 dmodel의 차원을 num_heads개로 나누어 dmodel/num_heads의 차원을 가지는 Q, K, V에 대해서 num_heads개의 병렬 어텐션을 수행한다. 다시 말해 위에서 설명한 어텐션이 8개로 병렬로 이루어지는 것인데, 이때 각각의 어텐션 값 행렬을 어텐션 헤드라고 부른다. 이때 가중치 행렬 Wq, Wk, Wv의 값은 8개의 어텐션 헤드마다 모두 다른 행렬이다.(같은 가중치로 병렬로 어텐션을 수행해봐야 같은 어텐션 값이 나올테니, 서로 다른 행렬로 수행하고 이 가중치들은 모두 훈련 과정에서 학습된다.) 즉, 서로 다른 가충치를 가지고 병렬로 어텐션을 수행하니 모든 어텐션 헤드들은 다른 시각으로 다른 정보들을 수집하게 되는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-02-16 오후 3.13.47.png&quot; data-origin-width=&quot;333&quot; data-origin-height=&quot;169&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/piZND/btrtpRIR84R/7YIrrwUwYkexEmusMwUBLK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/piZND/btrtpRIR84R/7YIrrwUwYkexEmusMwUBLK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/piZND/btrtpRIR84R/7YIrrwUwYkexEmusMwUBLK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpiZND%2FbtrtpRIR84R%2F7YIrrwUwYkexEmusMwUBLK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;333&quot; height=&quot;169&quot; data-filename=&quot;스크린샷 2022-02-16 오후 3.13.47.png&quot; data-origin-width=&quot;333&quot; data-origin-height=&quot;169&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;병렬 어텐션을 모두 수행하였다면, 모든 어텐션 헤드를 연결(concatenate)한다. 모두 연결된 어텐션 헤드 행렬의 크기는 (seq_len, dmodel)이 된다. 즉, 입력으로 들어온 행렬의 크기와 같게 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-02-16 오후 3.16.31.png&quot; data-origin-width=&quot;521&quot; data-origin-height=&quot;268&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bvks5O/btrtwrn8G4s/J2xmTGP7wC7eoZq4KGX3gk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bvks5O/btrtwrn8G4s/J2xmTGP7wC7eoZq4KGX3gk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bvks5O/btrtwrn8G4s/J2xmTGP7wC7eoZq4KGX3gk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbvks5O%2Fbtrtwrn8G4s%2FJ2xmTGP7wC7eoZq4KGX3gk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;521&quot; height=&quot;268&quot; data-filename=&quot;스크린샷 2022-02-16 오후 3.16.31.png&quot; data-origin-width=&quot;521&quot; data-origin-height=&quot;268&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어텐션 헤드를 모두 연결한 행렬은 또 다른 가중치 행렬 Wo를 곱하게 되는데, 이렇게 나온 결과 행렬이 멀티-헤드 어텐션의 최종 출력이 된다. 즉, 멀티-헤드 어텐션 행렬은 인코더의 입력이었던 문장 행렬의 (seq_len, dmodel) 크기와 동일하다. 다시 말해 인코더의 첫번째 서브층인 멀티-헤드 어텐션 서브층을 지나서 나온 출력이 인코더의 입력 행렬크기로 유지되고 있는 것이다. 또한 다음 서브층인 포지션 와이즈 피드 포워드 층으로 들어가는 입력 크기도 그대로 유지되서 들어가야한다.&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;패딩 마스크(Padding Mask)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스케일드 닷 프로덕트 어텐션 함수 내부를 보면 mask라는 인자가 존재한다. 이는 입력 문장에 &amp;lt;PAD&amp;gt; 토큰이 있을 경우 어텐션에서 제외하기 위한 연산을 위함이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-02-16 오후 3.24.09.png&quot; data-origin-width=&quot;535&quot; data-origin-height=&quot;183&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btB590/btrtrqjjt9I/K7DkKnoK8LBg1mdvgKk6xK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btB590/btrtrqjjt9I/K7DkKnoK8LBg1mdvgKk6xK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btB590/btrtrqjjt9I/K7DkKnoK8LBg1mdvgKk6xK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbtB590%2Fbtrtrqjjt9I%2FK7DkKnoK8LBg1mdvgKk6xK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;535&quot; height=&quot;183&quot; data-filename=&quot;스크린샷 2022-02-16 오후 3.24.09.png&quot; data-origin-width=&quot;535&quot; data-origin-height=&quot;183&quot;/&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;사실 패딩을 위한 토큰은 실질적인 의미를 담은 단어는 아니고, 시퀀스의 길이를 맞춰주기 위한 더미 토큰이기 때문에 트랜스포머에서는 Key의 경우 패딩 토큰이 존재한다면 유사도를 구하지 않도록 마스킹을 해준다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-02-16 오후 3.25.07.png&quot; data-origin-width=&quot;324&quot; data-origin-height=&quot;147&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/be9fd5/btrtrqjjw0q/cdaqfhseUDlvi30MKtl111/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/be9fd5/btrtrqjjw0q/cdaqfhseUDlvi30MKtl111/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/be9fd5/btrtrqjjw0q/cdaqfhseUDlvi30MKtl111/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbe9fd5%2Fbtrtrqjjw0q%2FcdaqfhseUDlvi30MKtl111%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;324&quot; height=&quot;147&quot; data-filename=&quot;스크린샷 2022-02-16 오후 3.25.07.png&quot; data-origin-width=&quot;324&quot; data-origin-height=&quot;147&quot;/&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;마스킹을 하는 방법은 어텐션 스코어 행렬의 마스킹 위치에 매우 작은 음수값을 넣는것이다. 즉 이렇게 되면, 해당 위치는 소프트맥스 함수를 지나도 0에 아주 가까운 값이 되기에 유사도 구하는데 큰 역할을 하지 못하게 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-02-16 오후 3.26.01.png&quot; data-origin-width=&quot;228&quot; data-origin-height=&quot;147&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0zHal/btrtsVKi1B4/2fz9qTwDw0DXwzcKOWsm6k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0zHal/btrtsVKi1B4/2fz9qTwDw0DXwzcKOWsm6k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0zHal/btrtsVKi1B4/2fz9qTwDw0DXwzcKOWsm6k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0zHal%2FbtrtsVKi1B4%2F2fz9qTwDw0DXwzcKOWsm6k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;228&quot; height=&quot;147&quot; data-filename=&quot;스크린샷 2022-02-16 오후 3.26.01.png&quot; data-origin-width=&quot;228&quot; data-origin-height=&quot;147&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;포지션-와이즈 피드 포워드 신경망(Position-wise FFNN)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;포지션-와이즈 피드 포워드 신경망은 인코더와 디코더에서 공통적으로 가지고 있는 서브층이다. 쉽게 말하면 포지션-와이즈 피드 포워드 신경망은 완전 연결 FFNN(Fully-connected FFNN)이라고 해석할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-02-16 오후 3.28.48.png&quot; data-origin-width=&quot;327&quot; data-origin-height=&quot;34&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mC9sB/btrtqNlr5yN/MHh1F9aODkvqKaLXu1US6k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mC9sB/btrtqNlr5yN/MHh1F9aODkvqKaLXu1US6k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mC9sB/btrtqNlr5yN/MHh1F9aODkvqKaLXu1US6k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmC9sB%2FbtrtqNlr5yN%2FMHh1F9aODkvqKaLXu1US6k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;327&quot; height=&quot;34&quot; data-filename=&quot;스크린샷 2022-02-16 오후 3.28.48.png&quot; data-origin-width=&quot;327&quot; data-origin-height=&quot;34&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 수식을 그림으로 표현하면 아래와 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-02-16 오후 3.29.15.png&quot; data-origin-width=&quot;211&quot; data-origin-height=&quot;200&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ctvM88/btrtqNFLV4F/w5elzGkz41DPKApwLNeBI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ctvM88/btrtqNFLV4F/w5elzGkz41DPKApwLNeBI1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ctvM88/btrtqNFLV4F/w5elzGkz41DPKApwLNeBI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FctvM88%2FbtrtqNFLV4F%2Fw5elzGkz41DPKApwLNeBI1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;211&quot; height=&quot;200&quot; data-filename=&quot;스크린샷 2022-02-16 오후 3.29.15.png&quot; data-origin-width=&quot;211&quot; data-origin-height=&quot;200&quot;/&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;여기서 x는 멀티 헤드 어텐션의 결과로 나온 (seq_len, dmodel)의 크기를 가지는 행렬을 말한다. 가중치 행렬 W1은 (dmodel, dff)의 크기를 가지고, 가중치 행렬 W2는 (dff, dmodel)의 크기를 가진다. 논문에서는 은닉층의 크기인 dff는 2048의 크기를 가진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 매개변수 W1, W2, b1, b2는 하나의 인코더 층 내에서는 다른 문장, 다른 단어들마다 정확하게 동일한 행렬을 가진다. 하지만 서로 다른 인코더 층마다는 다른 값을 가진다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-02-16 오후 3.31.27.png&quot; data-origin-width=&quot;558&quot; data-origin-height=&quot;237&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QZqfe/btrtvsOpLpe/6D45LDBYB2SM9WEmPVLBk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QZqfe/btrtvsOpLpe/6D45LDBYB2SM9WEmPVLBk1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QZqfe/btrtvsOpLpe/6D45LDBYB2SM9WEmPVLBk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQZqfe%2FbtrtvsOpLpe%2F6D45LDBYB2SM9WEmPVLBk1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;558&quot; height=&quot;237&quot; data-filename=&quot;스크린샷 2022-02-16 오후 3.31.27.png&quot; data-origin-width=&quot;558&quot; data-origin-height=&quot;237&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 기억해야할 것은 서브층을 모두 지나게 된 은닉층의 행렬크기를 보면 입력과 동일하다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;잔차 연결(Residual connection)과 층 정규화(Layer Normalization)&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-02-16 오후 3.35.56.png&quot; data-origin-width=&quot;448&quot; data-origin-height=&quot;237&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ntWZt/btrtqDDF6S1/BEltpwRgD66mGNyxiTVwpk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ntWZt/btrtqDDF6S1/BEltpwRgD66mGNyxiTVwpk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ntWZt/btrtqDDF6S1/BEltpwRgD66mGNyxiTVwpk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FntWZt%2FbtrtqDDF6S1%2FBEltpwRgD66mGNyxiTVwpk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;448&quot; height=&quot;237&quot; data-filename=&quot;스크린샷 2022-02-16 오후 3.35.56.png&quot; data-origin-width=&quot;448&quot; data-origin-height=&quot;237&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트랜스포머에서는 두 개의 서브층을 가진 인코더에 추가적으로 사용하는 Add &amp;amp; Norm이라는 기법이 있다. 그림을 보면 화살표가 서브층의 이전의 입력에서 시작되어 서브층의 출력부분을 향하고 있다.&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;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-02-16 오후 3.38.28.png&quot; data-origin-width=&quot;161&quot; data-origin-height=&quot;226&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uF7RJ/btrtwHRQpeH/OFKBo1Q8mBgEkqp4V7u1X1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uF7RJ/btrtwHRQpeH/OFKBo1Q8mBgEkqp4V7u1X1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uF7RJ/btrtwHRQpeH/OFKBo1Q8mBgEkqp4V7u1X1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuF7RJ%2FbtrtwHRQpeH%2FOFKBo1Q8mBgEkqp4V7u1X1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;161&quot; height=&quot;226&quot; data-filename=&quot;스크린샷 2022-02-16 오후 3.38.28.png&quot; data-origin-width=&quot;161&quot; data-origin-height=&quot;226&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 그림을 보면, 어떤 함수의 입력값과 함수의 결과를 더하고 있는 H(x) 함수가 있다. 여기서 어떤 함수 F(x)가 트랜스포머에서는 각 서브층을 뜻하게 되는데, 쉽게 말해 잔차 연결은 서브층의 입력과 출력을 더한것과 같다. 앞서 설명했듯이 트랜스포머에서 서브층의 입력과 출력은 동일한 차원을 가지고 있으므로, 서브층의 입력과 서브층의 출력은 행렬 덧셈 연산이 가능하다. &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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-02-16 오후 3.40.21.png&quot; data-origin-width=&quot;568&quot; data-origin-height=&quot;171&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YgxPE/btrtpQwyRnE/kyG5vvopq69CpOGXCwxexK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YgxPE/btrtpQwyRnE/kyG5vvopq69CpOGXCwxexK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YgxPE/btrtpQwyRnE/kyG5vvopq69CpOGXCwxexK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYgxPE%2FbtrtpQwyRnE%2FkyG5vvopq69CpOGXCwxexK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;568&quot; height=&quot;171&quot; data-filename=&quot;스크린샷 2022-02-16 오후 3.40.21.png&quot; data-origin-width=&quot;568&quot; data-origin-height=&quot;171&quot;/&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;h4 data-ke-size=&quot;size20&quot;&gt;층 정규화&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전차 연결을 거친 결과는 이어서 층 정규화 과정을 거치게 된다. 잔차 연결의 입력을 x, 잔차 연결과 층 정규화 두 가지 연산을 모두 수행한 후의 결과 행렬을 LN이라고 했을 때, 잔차 연결 후 층 정규화 연산을 수식으로 표현하면 다음과 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-02-16 오후 3.41.52.png&quot; data-origin-width=&quot;295&quot; data-origin-height=&quot;43&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vBQX4/btrtrX2I7zW/mxrOBpU17xngnP3vsZd5R0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vBQX4/btrtrX2I7zW/mxrOBpU17xngnP3vsZd5R0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vBQX4/btrtrX2I7zW/mxrOBpU17xngnP3vsZd5R0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvBQX4%2FbtrtrX2I7zW%2FmxrOBpU17xngnP3vsZd5R0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;295&quot; height=&quot;43&quot; data-filename=&quot;스크린샷 2022-02-16 오후 3.41.52.png&quot; data-origin-width=&quot;295&quot; data-origin-height=&quot;43&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;층 정규화는 텐서의 마지막 차원에 대해서 평균과 분산을 구하고, 이를 가지고 어떤 수식을 통해 값을 정규화하여 학습을 돕는다. 여기서 텐서의 마지막 차원이란 트랜스포머에서는 dmodel 차원을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-02-16 오후 3.44.04.png&quot; data-origin-width=&quot;207&quot; data-origin-height=&quot;137&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bVvVnF/btrtt41ncF1/2yodkWM1wVbOSbPiK2LHr0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bVvVnF/btrtt41ncF1/2yodkWM1wVbOSbPiK2LHr0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bVvVnF/btrtt41ncF1/2yodkWM1wVbOSbPiK2LHr0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbVvVnF%2Fbtrtt41ncF1%2F2yodkWM1wVbOSbPiK2LHr0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;207&quot; height=&quot;137&quot; data-filename=&quot;스크린샷 2022-02-16 오후 3.44.04.png&quot; data-origin-width=&quot;207&quot; data-origin-height=&quot;137&quot;/&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;층 정규화를 위해서 우선, 화살표 방향으로 각각 평균과 분산을 구한다. dmodel 크기의 벡터는 아래와 같은 수식으로 정규화 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-02-16 오후 3.44.46.png&quot; data-origin-width=&quot;178&quot; data-origin-height=&quot;35&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZeIsi/btrtmf37XVw/sbeK8gtaifQ1MpqTjokR6K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZeIsi/btrtmf37XVw/sbeK8gtaifQ1MpqTjokR6K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZeIsi/btrtmf37XVw/sbeK8gtaifQ1MpqTjokR6K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZeIsi%2Fbtrtmf37XVw%2FsbeK8gtaifQ1MpqTjokR6K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;178&quot; height=&quot;35&quot; data-filename=&quot;스크린샷 2022-02-16 오후 3.44.46.png&quot; data-origin-width=&quot;178&quot; data-origin-height=&quot;35&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;층 정규화는 크게 두 가지 과정으로 이루어지는데, 첫번째는 평균과 분산을 통한 정규화, 두번째는 감마와 베타를 도입하는 것이다. 우선 평균과 분산을 통해 벡터 x를 정규화 해준다. 여기서 평균과 분산값은 스칼라값이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-02-16 오후 3.46.41.png&quot; data-origin-width=&quot;150&quot; data-origin-height=&quot;64&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dgohlq/btrtt39b5vB/LcpWhQCMcDBHfwR08kx0HK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dgohlq/btrtt39b5vB/LcpWhQCMcDBHfwR08kx0HK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dgohlq/btrtt39b5vB/LcpWhQCMcDBHfwR08kx0HK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdgohlq%2Fbtrtt39b5vB%2FLcpWhQCMcDBHfwR08kx0HK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;150&quot; height=&quot;64&quot; data-filename=&quot;스크린샷 2022-02-16 오후 3.46.41.png&quot; data-origin-width=&quot;150&quot; data-origin-height=&quot;64&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 입실론은 분모가 0이 되는 것을 방지하는 값이다. 마지막으로 감마와 베타를 도입한 최종 정규화 수식은 아래와 같다.(여기서 감마와 베타는 처음에 0과 1로 초기화 된다.)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-02-16 오후 3.47.33.png&quot; data-origin-width=&quot;552&quot; data-origin-height=&quot;164&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XdOm9/btrtrYm3bsn/ykmfvk7mHH09GFioOJe9Kk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XdOm9/btrtrYm3bsn/ykmfvk7mHH09GFioOJe9Kk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XdOm9/btrtrYm3bsn/ykmfvk7mHH09GFioOJe9Kk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXdOm9%2FbtrtrYm3bsn%2Fykmfvk7mHH09GFioOJe9Kk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;552&quot; height=&quot;164&quot; data-filename=&quot;스크린샷 2022-02-16 오후 3.47.33.png&quot; data-origin-width=&quot;552&quot; data-origin-height=&quot;164&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;케라스에서는 층 정규화를 위해 LayerNormalization()을 제공한다. 지금까지 설명한 인코더 층에서의 출력은 디코더의 연산에 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-02-16 오후 3.54.51.png&quot; data-origin-width=&quot;563&quot; data-origin-height=&quot;373&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qSf3v/btrtpRPKSWO/AT5ao2NfyqaPo92cULkIIk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qSf3v/btrtpRPKSWO/AT5ao2NfyqaPo92cULkIIk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qSf3v/btrtpRPKSWO/AT5ao2NfyqaPo92cULkIIk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqSf3v%2FbtrtpRPKSWO%2FAT5ao2NfyqaPo92cULkIIk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;373&quot; data-filename=&quot;스크린샷 2022-02-16 오후 3.54.51.png&quot; data-origin-width=&quot;563&quot; data-origin-height=&quot;373&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;디코더의 첫번째 서브층: 셀프 어텐션과 룩-어헤드 마스크&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-02-16 오후 3.54.34.png&quot; data-origin-width=&quot;227&quot; data-origin-height=&quot;368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bq9OWR/btrtrXuVsv1/kkPhbHPB5IGr8Mnqrlnvy0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bq9OWR/btrtrXuVsv1/kkPhbHPB5IGr8Mnqrlnvy0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bq9OWR/btrtrXuVsv1/kkPhbHPB5IGr8Mnqrlnvy0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbq9OWR%2FbtrtrXuVsv1%2FkkPhbHPB5IGr8Mnqrlnvy0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;227&quot; height=&quot;368&quot; data-filename=&quot;스크린샷 2022-02-16 오후 3.54.34.png&quot; data-origin-width=&quot;227&quot; data-origin-height=&quot;368&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 그림과 같이 디코더도 인코더와 동일하게 포지셔널 인코딩 단계를 거친 후 입력으로 들어간다. 트랜스포머 또한 RNN셀로 이루어진 seq2seq와 마찬가지로 교사 강요를 사용하여 훈련되므로 학습 과정에서 디코더는 번역할 문장에 해당 되는 &lt;span style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;&amp;lt;sos&amp;gt; je suis &amp;eacute;tudiant의 문장 행렬을 한 번에 입력받는다. 그리고 디코더는 이 문장 행렬로부터 각 시점의 단어를 예측하도록 훈련한다. 하지만 여기서 문제가 있는데, 기존 RNN으로 이루어진 seq2seq는 매 시점마다 순차적으로 입력받으므로 다음 단어 예측에 현재 시점을 포함한 이전 시점에 입력된 단어들만 참고할 수 있다. 반면, 트랜스포머는 문장 행렬을 한번에 입력받아서 현재 시점의 단어를 예측하고자 할 때, 입력 문장 행렬로부터 미래 시점의 단어까지도 참고할 수 있는 현상이 발생되는데, 이를 위해 트랜스포머의 디코더에서는 현재 시점의 예측에서 현재 시점보다 미래에 있는 단어들을 참고하지 못하도록 룩-어헤드 마스크를 도입했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-02-16 오후 4.00.06.png&quot; data-origin-width=&quot;294&quot; data-origin-height=&quot;373&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/phKiB/btrtnC5SPfR/1oEKcDkv51IueGJaAGUxG1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/phKiB/btrtnC5SPfR/1oEKcDkv51IueGJaAGUxG1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/phKiB/btrtnC5SPfR/1oEKcDkv51IueGJaAGUxG1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FphKiB%2FbtrtnC5SPfR%2F1oEKcDkv51IueGJaAGUxG1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;294&quot; height=&quot;373&quot; data-filename=&quot;스크린샷 2022-02-16 오후 4.00.06.png&quot; data-origin-width=&quot;294&quot; data-origin-height=&quot;373&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;룩-어헤드 마스크는 디코더의 첫번째 서브층에서 이루어진다. 디코더의 첫번째 서브층인 멀티 헤드 셀프 어텐션 층은 인코더의 첫번째 서브층인 멀티 헤드 셀프 어텐션과 동일한 연산을 수행한다. 하지만 다른 점은 어텐션 스코어 행렬에서 미래 단어를 예측하지 못하도록 마스킹을 적용한다는 점만 다르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-02-16 오후 4.01.27.png&quot; data-origin-width=&quot;501&quot; data-origin-height=&quot;141&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WraHr/btrtwluKFIj/HjZtTHK0ozo1ukEV0QCjg1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WraHr/btrtwluKFIj/HjZtTHK0ozo1ukEV0QCjg1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WraHr/btrtwluKFIj/HjZtTHK0ozo1ukEV0QCjg1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWraHr%2FbtrtwluKFIj%2FHjZtTHK0ozo1ukEV0QCjg1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;501&quot; height=&quot;141&quot; data-filename=&quot;스크린샷 2022-02-16 오후 4.01.27.png&quot; data-origin-width=&quot;501&quot; data-origin-height=&quot;141&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;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-02-16 오후 4.02.07.png&quot; data-origin-width=&quot;216&quot; data-origin-height=&quot;133&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nqmrs/btrtt3OXKFi/KFJ1fuvHHgAPkxwD7gJkS0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nqmrs/btrtt3OXKFi/KFJ1fuvHHgAPkxwD7gJkS0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nqmrs/btrtt3OXKFi/KFJ1fuvHHgAPkxwD7gJkS0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fnqmrs%2Fbtrtt3OXKFi%2FKFJ1fuvHHgAPkxwD7gJkS0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;216&quot; height=&quot;133&quot; data-filename=&quot;스크린샷 2022-02-16 오후 4.02.07.png&quot; data-origin-width=&quot;216&quot; data-origin-height=&quot;133&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;디코더의 두번째 서브층: 인코더-디코더 어텐션&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디코더의 두번째 서브층은 멀티 헤드 어텐션을 수행은 하지만, 앞서 설명한것과는 다르게 셀프 어텐션이 아니다. 앞서 설명한 어텐션을 포함해 인코더-디코더 어텐션의 각 Q, K, V의 정의는 아래와 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1644995069656&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;인코더의 첫번째 서브층 : Query = Key = Value
디코더의 첫번째 서브층 : Query = Key = Value
디코더의 두번째 서브층 : Query : 디코더 행렬 / Key = Value : 인코더 행렬&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디코더의 두번째 서브층을 보면 아래 그림과 같이 인코더로부터 두 개의 화살표가 그려져있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-02-16 오후 4.05.15.png&quot; data-origin-width=&quot;161&quot; data-origin-height=&quot;133&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/co3txk/btrtqDKtPeu/tkJqf6IevUjjiAmLRBipMK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/co3txk/btrtqDKtPeu/tkJqf6IevUjjiAmLRBipMK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/co3txk/btrtqDKtPeu/tkJqf6IevUjjiAmLRBipMK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fco3txk%2FbtrtqDKtPeu%2FtkJqf6IevUjjiAmLRBipMK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;161&quot; height=&quot;133&quot; data-filename=&quot;스크린샷 2022-02-16 오후 4.05.15.png&quot; data-origin-width=&quot;161&quot; data-origin-height=&quot;133&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 개의 화살표는 각각 Key와 Value를 의미한다. Query는 그림과 같이 디코더의 첫번째 서브층의 결과 행렬을 뜻한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-02-16 오후 4.06.48.png&quot; data-origin-width=&quot;583&quot; data-origin-height=&quot;143&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzbqxu/btrtxkWtfhn/MkLOQO2Rimzk1GYG5DHqf1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzbqxu/btrtxkWtfhn/MkLOQO2Rimzk1GYG5DHqf1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzbqxu/btrtxkWtfhn/MkLOQO2Rimzk1GYG5DHqf1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbzbqxu%2FbtrtxkWtfhn%2FMkLOQO2Rimzk1GYG5DHqf1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;583&quot; height=&quot;143&quot; data-filename=&quot;스크린샷 2022-02-16 오후 4.06.48.png&quot; data-origin-width=&quot;583&quot; data-origin-height=&quot;143&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 외에 멀티 헤드 어텐션을 수행하는 과정은 다른 어텐션과 동일하다.&lt;/p&gt;</description>
      <category>머신러닝</category>
      <category>Bert</category>
      <category>self attention</category>
      <category>Transformer</category>
      <category>디코더</category>
      <category>멀티헤드어텐션</category>
      <category>셀프 어텐션</category>
      <category>스케일드닷프로덕트</category>
      <category>인코더</category>
      <category>트랜스포머</category>
      <author>여성게</author>
      <guid isPermaLink="true">https://coding-start.tistory.com/415</guid>
      <comments>https://coding-start.tistory.com/415#entry415comment</comments>
      <pubDate>Tue, 15 Feb 2022 20:03:53 +0900</pubDate>
    </item>
    <item>
      <title>딥러닝 - 어텐션 메커니즘(Attention Mechanism)</title>
      <link>https://coding-start.tistory.com/414</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;어텐션 메커니즘&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시퀀스-투-시퀀스(seq2seq) 모델 같은 경우는 인코더에서 입력 시퀀스를 컨텍스트 벡터(context vector)라는 하나의 고정된 크기의 벡터 표현으로 문장 시퀀스를 압축하고, 디코더는 해당 컨텍스트 벡터를 이용해 출력 시퀀스를 만들어낸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 이러한 RNN에 기반한 seq2seq 모델에는 아래와 같은 문제점이 있다.&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;RNN의 고질적인 문제인 기울기 소실(vanishing gradient)문제가 존재한다.&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;어텐션의 아이디어&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어텐션은 디코더에서 출력 단어를 예측하는 매 시점마다, 인코더에서의 전체 입력 문장을 다시 한번 참고하는 것이다. 하지만 여기서 중요한 것은 입력 문장 전체를 동일 비율로 참고하는 것이 아니라, 해당 시점에서 예측해야할 단어와 연관이 있는 입력 단어 부분에 조금 더 집중(attention)해서 보게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;어텐션 함수&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-02-15 오후 4.45.33.png&quot; data-origin-width=&quot;366&quot; data-origin-height=&quot;216&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DA891/btrtrYMJRQd/Pv2LoAeke4XkhU5hR2Ub01/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DA891/btrtrYMJRQd/Pv2LoAeke4XkhU5hR2Ub01/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DA891/btrtrYMJRQd/Pv2LoAeke4XkhU5hR2Ub01/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDA891%2FbtrtrYMJRQd%2FPv2LoAeke4XkhU5hR2Ub01%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;366&quot; height=&quot;216&quot; data-filename=&quot;스크린샷 2022-02-15 오후 4.45.33.png&quot; data-origin-width=&quot;366&quot; data-origin-height=&quot;216&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Attention(Q, K, V) = Attention Value&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;어텐션 함수는 주어진 쿼리(Q)에 대해서 모든 키(K)와의 유사도를 각각 구한 후 각 키와 매핑된 값(V)에 반영해준다. 그리고 유사도가 반영된 값을 모두 더해 리턴하는데, 여기서 리턴된 값을 어텐션 값(Attention Value)라고 한다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1644911466006&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Q = Query : t 시점의 디코더 셀에서의 은닉 상태
K = Keys : 모든 시점의 인코더 셀의 은닉 상태들
V = Values : 모든 시점의 인코더 셀의 은닉 상태들&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;닷-프로덕트 어텐션(Dot-Product Attention)&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-02-15 오후 4.51.59.png&quot; data-origin-width=&quot;582&quot; data-origin-height=&quot;463&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sMiQu/btrtlUE78wo/v8899y3yTcaw14ILQG9ZO1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sMiQu/btrtlUE78wo/v8899y3yTcaw14ILQG9ZO1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sMiQu/btrtlUE78wo/v8899y3yTcaw14ILQG9ZO1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsMiQu%2FbtrtlUE78wo%2Fv8899y3yTcaw14ILQG9ZO1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;582&quot; height=&quot;463&quot; data-filename=&quot;스크린샷 2022-02-15 오후 4.51.59.png&quot; data-origin-width=&quot;582&quot; data-origin-height=&quot;463&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;디코더의 세번째 시점의 LSTM 셀에서 출력 단어를 예측하는 예제&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;디코더의 세번째 LSTM 셀은 출력 단어를 예측하기 위해서 인코더의 모든 입력 단어들의 정보를 다시 한번 참고한다. 인코더 쪽의 소프트맥스 함수를 통해 나온 결과값은 I, am, a, student 단어 각각이 출력 단어(suis가 입력으로 들어간 세번째 시점의 LSTM 출력 )를 예측할 때 얼마나 도움이 되는지의 정도를 수치화한 값이다. 그리고 각 입력 단어가 디코더의 예측에 도움이 되는 정도를 수치화하여 하나의 정보로 담아서 디코더로 전송한다. 결과적으로 해당 어텐션 값을 이용해 디코더는 출력 단어를 더 정확하게 예측할 확률이 높아진다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;어텐션 스코어&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-02-15 오후 4.57.30.png&quot; data-origin-width=&quot;625&quot; data-origin-height=&quot;357&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/deU0rE/btrtqDClBH1/UsUyq0KH6hWC24IrBBUAk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/deU0rE/btrtqDClBH1/UsUyq0KH6hWC24IrBBUAk1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/deU0rE/btrtqDClBH1/UsUyq0KH6hWC24IrBBUAk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdeU0rE%2FbtrtqDClBH1%2FUsUyq0KH6hWC24IrBBUAk1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;625&quot; height=&quot;357&quot; data-filename=&quot;스크린샷 2022-02-15 오후 4.57.30.png&quot; data-origin-width=&quot;625&quot; data-origin-height=&quot;357&quot;/&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;인코더의 각 시점의 은닉 상태(hidden state)를 각각 h1,h2,..hn이라고 하고, 디코더의 현재 시점 t에서의 디코더의 은닉 상태를 st라고 한다. 다시 돌아가서, 디코더의 현재 시점 t에서 필요한 입력값은 바로 이전 시점인 t-1의 은닉 상태와 이전 시점 t-1에서 나온 출력단어이다. 그런데 어텐션 메커니즘에서는 출력 단어 예측에 어텐션 값이라는 새로운 값이 필요하다. 여기서 t시점의 출력을 예측하기 위한 어텐션 값을 at라고 하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 어텐션 스코어(Attention score)를 구해보자. 어텐션 스코어란 현재 디코더의 시점 t에서 단어를 예측하기 위해, 인코더의 모든 은닉 상태 각각이 디코더의 현 시점의 은닉상태 st와 얼마나 유사한지를 판단하는 스코어값이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;닷-프로덕트 어텐션에서는 이 스코어 값을 구하기 위해 st를 전치(transpose)하고 각 은닉 상태와 내적(dot product)을 수행한다. 아래 그림은 st과 인코더 i번째의 은닉 상태의 어텐션 스코어의 계산 방법이다. 여기서 어텐션 스코어 값은 모두 스칼라 값을 가진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-02-15 오후 5.04.08.png&quot; data-origin-width=&quot;221&quot; data-origin-height=&quot;152&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/n8MJH/btrtqCpUfIV/Cbqae22o7IQO3gNn08yXQ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/n8MJH/btrtqCpUfIV/Cbqae22o7IQO3gNn08yXQ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/n8MJH/btrtqCpUfIV/Cbqae22o7IQO3gNn08yXQ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fn8MJH%2FbtrtqCpUfIV%2FCbqae22o7IQO3gNn08yXQ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;221&quot; height=&quot;152&quot; data-filename=&quot;스크린샷 2022-02-15 오후 5.04.08.png&quot; data-origin-width=&quot;221&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-02-15 오후 5.04.35.png&quot; data-origin-width=&quot;668&quot; data-origin-height=&quot;152&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ScYRA/btrtpPJOmyo/YQi4sPsmH3AyUKuhi07Rwk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ScYRA/btrtpPJOmyo/YQi4sPsmH3AyUKuhi07Rwk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ScYRA/btrtpPJOmyo/YQi4sPsmH3AyUKuhi07Rwk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FScYRA%2FbtrtpPJOmyo%2FYQi4sPsmH3AyUKuhi07Rwk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;668&quot; height=&quot;152&quot; data-filename=&quot;스크린샷 2022-02-15 오후 5.04.35.png&quot; data-origin-width=&quot;668&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;소프트맥스 함수를 통해 어텐션 분포 구하기&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-02-15 오후 5.05.09.png&quot; data-origin-width=&quot;668&quot; data-origin-height=&quot;337&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9z1ZY/btrtqgHfgJS/Fco8LPBnJ6sToKAax1aqt0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9z1ZY/btrtqgHfgJS/Fco8LPBnJ6sToKAax1aqt0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9z1ZY/btrtqgHfgJS/Fco8LPBnJ6sToKAax1aqt0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9z1ZY%2FbtrtqgHfgJS%2FFco8LPBnJ6sToKAax1aqt0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;668&quot; height=&quot;337&quot; data-filename=&quot;스크린샷 2022-02-15 오후 5.05.09.png&quot; data-origin-width=&quot;668&quot; data-origin-height=&quot;337&quot;/&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;위 et에 소프트 맥스 함수를 적용하여, 모든 값을 합하면 1이 되는 확률 분포를 얻어낸다. 이를 어텐션 분포라고 하며, 각각의 값은 어텐션 가충치(Attention Weight)라고 한다. &lt;span style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;예를 들어 소프트맥스 함수를 적용하여 얻은 출력값인 I, am, a, student의 어텐션 가중치를 각각 0.1, 0.4, 0.1, 0.4라고 하고, 이들의 합은 1이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;디코더의 시점 t에서의 어텐션 가중치의 모음값인 어텐션 분포를&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&amp;alpha;t&lt;span style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;이라고 할 때,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&amp;alpha;t&lt;span style=&quot;background-color: #ffffff; 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;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-02-15 오후 5.07.33.png&quot; data-origin-width=&quot;145&quot; data-origin-height=&quot;54&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cSkeod/btrtfFV9GjF/esu4bkuZzkKJncfiA3uVA1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cSkeod/btrtfFV9GjF/esu4bkuZzkKJncfiA3uVA1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cSkeod/btrtfFV9GjF/esu4bkuZzkKJncfiA3uVA1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcSkeod%2FbtrtfFV9GjF%2Fesu4bkuZzkKJncfiA3uVA1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;145&quot; height=&quot;54&quot; data-filename=&quot;스크린샷 2022-02-15 오후 5.07.33.png&quot; data-origin-width=&quot;145&quot; data-origin-height=&quot;54&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 id=&quot;3-attention-value&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;각 인코더의 어텐션 가중치와 은닉 상태를 가중합하여 어텐션 값(Attention Value)을 구한다.&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-02-15 오후 5.09.21.png&quot; data-origin-width=&quot;621&quot; data-origin-height=&quot;390&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sEDrp/btrtjPxQbCn/hSbKbQJMBSJZw84f5LBqkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sEDrp/btrtjPxQbCn/hSbKbQJMBSJZw84f5LBqkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sEDrp/btrtjPxQbCn/hSbKbQJMBSJZw84f5LBqkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsEDrp%2FbtrtjPxQbCn%2FhSbKbQJMBSJZw84f5LBqkk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;621&quot; height=&quot;390&quot; data-filename=&quot;스크린샷 2022-02-15 오후 5.09.21.png&quot; data-origin-width=&quot;621&quot; data-origin-height=&quot;390&quot;/&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;어텐션 값을 얻기 위해 각 인코더의 은닉 상태와 어텐션 가중치 값들을 곱하고, 최종적으로 모두 더한다. 요약하자면 가중합(weighted Sum)을 진행한다. 수식으로 표현하면 아래와 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-02-15 오후 5.10.34.png&quot; data-origin-width=&quot;117&quot; data-origin-height=&quot;64&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EGh6H/btrtmUEVS6n/wCvf4gB4w06h4e0NvDwpxK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EGh6H/btrtmUEVS6n/wCvf4gB4w06h4e0NvDwpxK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EGh6H/btrtmUEVS6n/wCvf4gB4w06h4e0NvDwpxK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEGh6H%2FbtrtmUEVS6n%2FwCvf4gB4w06h4e0NvDwpxK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;117&quot; height=&quot;64&quot; data-filename=&quot;스크린샷 2022-02-15 오후 5.10.34.png&quot; data-origin-width=&quot;117&quot; data-origin-height=&quot;64&quot;/&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;이러한 어텐션 값은 종종 인코더의 문맥을 포함하고 있다고 하여, 컨텍스트 벡터(Context Vector)라고도 불린다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 id=&quot;4-t-concatenate&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;어텐션 값과 디코더의 t 시점의 은닉 상태를 연결한다.(Concatenate)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-02-15 오후 5.12.21.png&quot; data-origin-width=&quot;697&quot; data-origin-height=&quot;350&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SMl8L/btrtoA7dKov/Wfin330qN4KKUo4LRhA8rk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SMl8L/btrtoA7dKov/Wfin330qN4KKUo4LRhA8rk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SMl8L/btrtoA7dKov/Wfin330qN4KKUo4LRhA8rk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSMl8L%2FbtrtoA7dKov%2FWfin330qN4KKUo4LRhA8rk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;697&quot; height=&quot;350&quot; data-filename=&quot;스크린샷 2022-02-15 오후 5.12.21.png&quot; data-origin-width=&quot;697&quot; data-origin-height=&quot;350&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;어텐션 함수의 최종값인 어텐션 값&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;at&lt;span style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;을 구했고 그리고&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;at&lt;span style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;를&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;st&lt;span style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;와 결합(concatenate)하여 하나의 벡터 Vt로 만드는 작업을 수행한다. &lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;그리고 이&lt;span&gt; &lt;span style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;Vt&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;를&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;y^&lt;span style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;예측 연산의 입력으로 사용하므로서 인코더로부터 얻은 정보를 활용하여&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;y^&lt;span style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;를 좀 더 잘 예측할 수 있게 된다. 이것이 어텐션 메커니즘의 핵심이다. 하지만 여기서 나온 Vt를 바로 출력의 입력으로는 사용하지 못하고, 아래 그림과 같이 출력층으로 보내기 전에 신경망 연산을 통해 기존 은닉층과 같은 벡터 차원으로 만들어준 후에 나온 St^값을 입력으로 넣는다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-02-15 오후 5.20.09.png&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;224&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yNmuD/btrtjPxRgUm/0Vl2IT3ztKKcYsmg22HkyK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yNmuD/btrtjPxRgUm/0Vl2IT3ztKKcYsmg22HkyK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yNmuD/btrtjPxRgUm/0Vl2IT3ztKKcYsmg22HkyK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyNmuD%2FbtrtjPxRgUm%2F0Vl2IT3ztKKcYsmg22HkyK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;480&quot; height=&quot;224&quot; data-filename=&quot;스크린샷 2022-02-15 오후 5.20.09.png&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;224&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-02-15 오후 5.20.26.png&quot; data-origin-width=&quot;488&quot; data-origin-height=&quot;68&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LS88e/btrtk0Z8akV/MJGDlDnNuvXPgoikQm8GF0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LS88e/btrtk0Z8akV/MJGDlDnNuvXPgoikQm8GF0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LS88e/btrtk0Z8akV/MJGDlDnNuvXPgoikQm8GF0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLS88e%2Fbtrtk0Z8akV%2FMJGDlDnNuvXPgoikQm8GF0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;488&quot; height=&quot;68&quot; data-filename=&quot;스크린샷 2022-02-15 오후 5.20.26.png&quot; data-origin-width=&quot;488&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;위와 같이 St^를 출력층의 입력으로 사용해 예측 벡터를 얻는다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-02-15 오후 5.21.19.png&quot; data-origin-width=&quot;488&quot; data-origin-height=&quot;68&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cxm75M/btrtjQjazuT/EdXSM8iwC04ImLF3iP4Wx1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cxm75M/btrtjQjazuT/EdXSM8iwC04ImLF3iP4Wx1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cxm75M/btrtjQjazuT/EdXSM8iwC04ImLF3iP4Wx1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcxm75M%2FbtrtjQjazuT%2FEdXSM8iwC04ImLF3iP4Wx1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;488&quot; height=&quot;68&quot; data-filename=&quot;스크린샷 2022-02-15 오후 5.21.19.png&quot; data-origin-width=&quot;488&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;위에서 설명한 dot-product 어텐션 말고도 다양한 어텐션 종류가 있다. 하지만 그 차이는 중간에 수식 차이이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-02-15 오후 5.22.20.png&quot; data-origin-width=&quot;843&quot; data-origin-height=&quot;441&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rmFjJ/btrtfEXoJbY/oYkQKdGOcaIA05z2DubX10/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rmFjJ/btrtfEXoJbY/oYkQKdGOcaIA05z2DubX10/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rmFjJ/btrtfEXoJbY/oYkQKdGOcaIA05z2DubX10/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrmFjJ%2FbtrtfEXoJbY%2FoYkQKdGOcaIA05z2DubX10%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;843&quot; height=&quot;441&quot; data-filename=&quot;스크린샷 2022-02-15 오후 5.22.20.png&quot; data-origin-width=&quot;843&quot; data-origin-height=&quot;441&quot;/&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;위에서 St는 Query, Hi는 keys, Wa와 Wb는 학습 가능한 가중치 행렬이다. 즉, 쿼리는 디코더의 현재 시점의 은닉 상태, 키는 모든 인코더의 은닉 상태를 뜻하며 이러한 값들을 이용해 최종적인 어텐션 값을 구하게 된다.&lt;/p&gt;</description>
      <category>머신러닝</category>
      <category>Attention</category>
      <category>Bert</category>
      <category>LSTM</category>
      <category>RNN</category>
      <category>디코더</category>
      <category>딥러닝</category>
      <category>머신러닝</category>
      <category>버트</category>
      <category>어텐션</category>
      <category>인코더</category>
      <author>여성게</author>
      <guid isPermaLink="true">https://coding-start.tistory.com/414</guid>
      <comments>https://coding-start.tistory.com/414#entry414comment</comments>
      <pubDate>Tue, 15 Feb 2022 17:18:02 +0900</pubDate>
    </item>
    <item>
      <title>자연어 처리 개요</title>
      <link>https://coding-start.tistory.com/413</link>
      <description>&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;622&quot; data-origin-height=&quot;802&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/K0xf6/btrq9WS0xEC/krdLMnflre0LtD14i3ZYiK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/K0xf6/btrq9WS0xEC/krdLMnflre0LtD14i3ZYiK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/K0xf6/btrq9WS0xEC/krdLMnflre0LtD14i3ZYiK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FK0xf6%2Fbtrq9WS0xEC%2FkrdLMnflre0LtD14i3ZYiK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;350&quot; height=&quot;451&quot; data-origin-width=&quot;622&quot; data-origin-height=&quot;802&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;br&gt;해당 포스팅은 위 이미지의 책의 챕터3을 읽은 리뷰입니다. :)&lt;br&gt; &lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;자연어 처리 개요&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;단어 표현&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;텍스트를 자연어 처리를 위한 모델에 적용할 수 있게 언어적인 특성을 반영해서 단어를 수치화하는 방법을 찾는 것이다. 보통 단어를 수치화할 때는 주로 벡터로 표현한다. 따라서 단어 표현은 &quot;단어 임베딩(word embedding)&quot; 또는 &quot;단어 벡터(word vector)&quot;로 표현한다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;단어 임베딩 기법&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt; 
 &lt;li&gt;원-핫 인코딩(one-hot encoding) : 단어를 하나의 벡터로 표현하며 각 단어는 벡터 값 가운데 하나만 1이라는 값을 가지고 나머지는 모두 0값을 가짐.&lt;/li&gt; 
 &lt;li&gt;분포 가설(Distributed hypothesis) 
  &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;예측 방법&amp;nbsp; 
    &lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt; 
     &lt;li&gt;Word2vec 
      &lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt; 
       &lt;li&gt;CBOW(Continuous Bag of Words) : 어떤 단어를 문백 안의 주변 단어들을 통해 예측하는 방법(창욱은 냉장고에서 _____ 꺼내서 먹었다.)&lt;/li&gt; 
       &lt;li&gt;Skip-Gram : 어떤 단어를 가지고 특정 문맥 안의 주변 단어들을 예측하는 방법(____ ________ 음식을 _____ _____)&lt;/li&gt; 
      &lt;/ul&gt; &lt;/li&gt; 
     &lt;li&gt;NNLM(Neural Network Language Model)&lt;/li&gt; 
     &lt;li&gt;RNNLM(Recurrent&amp;nbsp;Neural Network Language Model)&lt;/li&gt; 
    &lt;/ul&gt; &lt;/li&gt; 
   &lt;li&gt;카운트 기반 방법과 예측 방법 모두를 차용해서 쓰는 단어 임베딩 기법 
    &lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt; 
     &lt;li&gt;Glove&lt;/li&gt; 
    &lt;/ul&gt; &lt;/li&gt; 
  &lt;/ul&gt; &lt;/li&gt; 
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;텍스트 분류&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;텍스트 분류는 자연어 처리 문제 중 가장 대표적이고 많이 접하는 문제다. 자연어 처리 기술을 활용해 특정 텍스트를 사람들이 정한 몇 가지 범주(class) 중 어느 범주에 속하는지 분류하는 문제다.&lt;br&gt; &lt;br&gt;분류해야 할 범주의 수에 따라 문제를 구분&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
 &lt;li&gt;이진 분류(binary classification) : 2가지 범주에 대해 구분하는 문제&lt;/li&gt;
 &lt;li&gt;다중 범주 분류(Multi classfication) : 3가지 이상의 범주에 대해 구분하는 문제&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;지도 학습을 통한 텍스트 분류&lt;/h4&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;619&quot; data-origin-height=&quot;215&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oIBdf/btrq9WMcfAB/GudxUKMo2pRKarohom3Rg0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oIBdf/btrq9WMcfAB/GudxUKMo2pRKarohom3Rg0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oIBdf/btrq9WMcfAB/GudxUKMo2pRKarohom3Rg0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoIBdf%2Fbtrq9WMcfAB%2FGudxUKMo2pRKarohom3Rg0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;619&quot; height=&quot;215&quot; data-origin-width=&quot;619&quot; data-origin-height=&quot;215&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;br&gt;위 그림과 같이 지도 학습은 샘플(데이터)에 대해 각각 속한 범주에 대한 값(레이블)이 이미 주어져 있다. 따라서 주어진 범주로 글들을 모두 학습한 후 학습한 결과를 이용해 새로운 글의 범주를 예측하는 방법이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
 &lt;li&gt;KNN&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;/li&gt;
 &lt;li&gt;랜덤 포레스트&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;비지도 학습을 통한 텍스트 분류&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;br&gt; &lt;br&gt;비지도 학습에서는 데이터만 존재하고, 각 데이터는 범주로 미리 나눠져 있지 않다. 따라서 특성을 찾아내서 적당한 범주를 만들어 각 데이터를 나눈다.&lt;br&gt;비지도 학습을 통한 분류는 어떤 특정한 분류가 있는 것이 아니라 데이터의 특성에 따라 비슷한 데이터끼리 묶어주는 개념이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
 &lt;li&gt;K-means Clustering&lt;/li&gt;
 &lt;li&gt;Hierarchical Clustering&lt;/li&gt;
&lt;/ul&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;393&quot; data-origin-height=&quot;255&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cLJz5C/btrrdmpna0O/O9MN2uTjhHJbbXSQVKQvkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cLJz5C/btrrdmpna0O/O9MN2uTjhHJbbXSQVKQvkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cLJz5C/btrrdmpna0O/O9MN2uTjhHJbbXSQVKQvkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcLJz5C%2Fbtrrdmpna0O%2FO9MN2uTjhHJbbXSQVKQvkk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;393&quot; height=&quot;255&quot; data-origin-width=&quot;393&quot; data-origin-height=&quot;255&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;h3 data-ke-size=&quot;size23&quot;&gt;텍스트 유사도&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;텍스트 유사도란 말 그대로 텍스트가 얼마나 유사한지를 표현하는 방식 중 하나다. 유사도를 판단하는 척도는 매우 주관적이기 때문에 데이터를 구성하기가 쉽지 않고 정량화하는 데 한계가 있다. 이를 최대한 정량화해서 모델을 만드는 것이 중요하다.&lt;br&gt;일반적으로 유사도를 측정하기 위해 정량화하는 방법에는 여러 가지가 있다. 단순히 같은 단어의 개수를 사용해서 유사도를 판단하는 방법, 형태소로 나누어 형태소를 비교하는 방법, 자소 단위로 나누어 단어를 비교하는 방법 등 다양하다.&lt;br&gt;또한 딥러닝을 기반으로 텍스트의 유사도를 측정하는 방식도 있다. 단어, 형태소, 유사도의 종류에 상관 없이 텍스트를 벡터화한 후 벡터화된 각 문장 간의 유사도를 측정하는 방식이다.&lt;br&gt; &lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;자카드 유사도&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 집합(각 문자을 형태소 분리한 형태소의 집합)의 교집합인 공통된 단어의 개수를 두 집합의 합집합, 즉 전체 단어의 수로 나눈다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;유클리디언 유사도&lt;/h4&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;205&quot; data-origin-height=&quot;255&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/buwGKj/btrrbUm3155/U64ZJUQkRYvqDOQGUeWegK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/buwGKj/btrrbUm3155/U64ZJUQkRYvqDOQGUeWegK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/buwGKj/btrrbUm3155/U64ZJUQkRYvqDOQGUeWegK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbuwGKj%2FbtrrbUm3155%2FU64ZJUQkRYvqDOQGUeWegK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;205&quot; height=&quot;255&quot; data-origin-width=&quot;205&quot; data-origin-height=&quot;255&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;br&gt;n차원 공간에서 두 점 사이의 최단 거리를 구하는 접근법이다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;맨하탄 유사도&lt;/h4&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;205&quot; data-origin-height=&quot;255&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/duzA1K/btrrbThn8mb/bsnnMNNQRMNIz4XidqsCm1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/duzA1K/btrrbThn8mb/bsnnMNNQRMNIz4XidqsCm1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/duzA1K/btrrbThn8mb/bsnnMNNQRMNIz4XidqsCm1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FduzA1K%2FbtrrbThn8mb%2FbsnnMNNQRMNIz4XidqsCm1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;205&quot; height=&quot;255&quot; data-origin-width=&quot;205&quot; data-origin-height=&quot;255&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;br&gt;유클리디언 유사도처럼 출발점과 도착점까지를 가로지르지 않고 갈수 있는 최단 거리를 구하는 공식이다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;코사인 유사도&lt;/h4&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;253&quot; data-origin-height=&quot;201&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cdqxT3/btrq9XRT4Eb/7K3kZrbtkGUxLVSNcaNUT1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cdqxT3/btrq9XRT4Eb/7K3kZrbtkGUxLVSNcaNUT1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cdqxT3/btrq9XRT4Eb/7K3kZrbtkGUxLVSNcaNUT1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcdqxT3%2Fbtrq9XRT4Eb%2F7K3kZrbtkGUxLVSNcaNUT1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;253&quot; height=&quot;201&quot; data-origin-width=&quot;253&quot; data-origin-height=&quot;201&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;br&gt;코사인 유사도는 두 개의 벡터값에서 코사인 각도를 구하는 방법이다. 코사인 유사도 값은 -1과 1 사이의 값을 가지고 1에 가까울수록 유사하다는 것을 의미한다. 다른 유사도 접근법에 비해 일반적으로 성능이 좋은데,&lt;br&gt;이는 단순히 좌표상의 거리를 구하는 다른 유사도 측정 방법에 비해 코사인 유사도는 말 그대로 두 벡터 간의 각도를 구하는 것이라서 방향성의 개념이 더해지기 때문이다.&lt;/p&gt;</description>
      <category>머신러닝</category>
      <category>단어 임베딩</category>
      <category>맨하탄 유사도</category>
      <category>머신러닝</category>
      <category>벡터</category>
      <category>유사도</category>
      <category>유클리디언 유사도</category>
      <category>자연어처리</category>
      <category>코사인 유사도</category>
      <category>텍스트분류</category>
      <category>텐서플로</category>
      <author>여성게</author>
      <guid isPermaLink="true">https://coding-start.tistory.com/413</guid>
      <comments>https://coding-start.tistory.com/413#entry413comment</comments>
      <pubDate>Thu, 20 Jan 2022 10:49:17 +0900</pubDate>
    </item>
    <item>
      <title>웹플럭스에서 블록킹 연산의 영향은? 해결 방법?</title>
      <link>https://coding-start.tistory.com/412</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/shYR3/btrlmoAxpBY/plr6Y7zGiFCk7gRHlL43K0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/shYR3/btrlmoAxpBY/plr6Y7zGiFCk7gRHlL43K0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/shYR3/btrlmoAxpBY/plr6Y7zGiFCk7gRHlL43K0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FshYR3%2FbtrlmoAxpBY%2Fplr6Y7zGiFCk7gRHlL43K0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;720&quot; data-origin-width=&quot;1280&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;웹플럭스에서 블록킹 연산을 발생시키는 채널이 있다면, 이벤트 채널을 관리하는 이벤트 루프 자체에 블럭킹이 발생하기 때문에 전체적으로 요청 처리를 하나도 못하는 문제가 발생할 수 있다. 그렇기 때문에 블럭킹을 발생시키는 연산이 있을 경우 스케쥴을 분리시켜주는 것이 좋고, 실제로 리액터에서도 이러한 것을 고려해 스케쥴러 생성 팩토리 메서드를 제공한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2021-11-17 오후 6.13.03.png&quot; data-origin-width=&quot;3084&quot; data-origin-height=&quot;1070&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cKxwuV/btrloyh2Wpr/DeIRYTBoj6nAtZGlYVvr3k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cKxwuV/btrloyh2Wpr/DeIRYTBoj6nAtZGlYVvr3k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cKxwuV/btrloyh2Wpr/DeIRYTBoj6nAtZGlYVvr3k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcKxwuV%2Fbtrloyh2Wpr%2FDeIRYTBoj6nAtZGlYVvr3k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3084&quot; height=&quot;1070&quot; data-filename=&quot;스크린샷 2021-11-17 오후 6.13.03.png&quot; data-origin-width=&quot;3084&quot; data-origin-height=&quot;1070&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 두개의 팩토리 메서드는 non-blocking 연산을 위한 스케쥴러 팩토리 메서드이다. 오래 걸리는 연산 등을 이벤트 루프 쓰레드에서 분리하고 싶을 때 사용하며, 블럭킹 연산이 포함되지 않은 연산에서만 사용해야한다. 만약 블록킹 연산에 대해 스케쥴을 분리하고 싶다면 boundedElastic()을 이용하면 된다.&lt;/p&gt;
&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;widthOrigin&quot; data-filename=&quot;스크린샷 2021-11-17 오후 6.08.30.png&quot; data-origin-width=&quot;3084&quot; data-origin-height=&quot;976&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Osnex/btrliNURWxv/rKIM0qfENIle00VH0xR2NK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Osnex/btrliNURWxv/rKIM0qfENIle00VH0xR2NK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Osnex/btrliNURWxv/rKIM0qfENIle00VH0xR2NK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOsnex%2FbtrliNURWxv%2FrKIM0qfENIle00VH0xR2NK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3084&quot; height=&quot;976&quot; data-filename=&quot;스크린샷 2021-11-17 오후 6.08.30.png&quot; data-origin-width=&quot;3084&quot; data-origin-height=&quot;976&quot;/&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2021-11-17 오후 6.09.24.png&quot; data-origin-width=&quot;3084&quot; data-origin-height=&quot;976&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/12jd7/btrlpW3NIYf/GHv0JndJYJmkk9msTA8MkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/12jd7/btrlpW3NIYf/GHv0JndJYJmkk9msTA8MkK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/12jd7/btrlpW3NIYf/GHv0JndJYJmkk9msTA8MkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F12jd7%2FbtrlpW3NIYf%2FGHv0JndJYJmkk9msTA8MkK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3084&quot; height=&quot;976&quot; data-filename=&quot;스크린샷 2021-11-17 오후 6.09.24.png&quot; data-origin-width=&quot;3084&quot; data-origin-height=&quot;976&quot;/&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;위 두개의 메서드는 동기 메서드이며, 첫번째 메서드는 동기코드이며 블럭킹을 유발시키는 코드이다.(InterruptedException 발생) 세번째 메서드는 블록킹 콜이 없는 비동기 메서드이다. 첫번째 두번째 메서드는 사용하는 쪽에서 스케쥴 분리를 신경쓰면 되고 세번째 메서드는 해당 메서드 안에서 스케쥴 분리 처리를 해주어야한다. 근데 진짜 위 메서드 시그니처를 지키면 블록킹 콜이 발생하지 않을까?.. 이것을 찾기위한 도구로 BlockHound가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2021-11-17 오후 6.18.43.png&quot; data-origin-width=&quot;3084&quot; data-origin-height=&quot;1656&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WjKlq/btrlof4bB4Y/FLKO2wOxXfnQXUaKpGt8p1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WjKlq/btrlof4bB4Y/FLKO2wOxXfnQXUaKpGt8p1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WjKlq/btrlof4bB4Y/FLKO2wOxXfnQXUaKpGt8p1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWjKlq%2Fbtrlof4bB4Y%2FFLKO2wOxXfnQXUaKpGt8p1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3084&quot; height=&quot;1656&quot; data-filename=&quot;스크린샷 2021-11-17 오후 6.18.43.png&quot; data-origin-width=&quot;3084&quot; data-origin-height=&quot;1656&quot;/&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;BlockHound는 운영환경에 같이 띄우면 안된다.(실제 바이트 코드등의 영향을 줄 수 있어서) 그렇기에 테스트 코드와 결합하여 사전에 블록킹 연산을 찾아내는 용도로만 사용하자 !(비효율적인 연산 등을 잡는 역할로는 사용 불가)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출처: IfKakao&lt;/p&gt;</description>
      <category>Web/Spring</category>
      <category>blocking</category>
      <category>non-block</category>
      <category>Spring</category>
      <category>Webflux</category>
      <category>논블록킹</category>
      <category>블록킹</category>
      <category>스프링</category>
      <category>웹플럭스</category>
      <category>자바</category>
      <author>여성게</author>
      <guid isPermaLink="true">https://coding-start.tistory.com/412</guid>
      <comments>https://coding-start.tistory.com/412#entry412comment</comments>
      <pubDate>Wed, 17 Nov 2021 18:21:17 +0900</pubDate>
    </item>
    <item>
      <title>Kotlin - 코틀린 타입 시스템(널이 될 수 있는 타입)</title>
      <link>https://coding-start.tistory.com/399</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9TA4U/btqI4TxUzg8/qCkFbXR6Hv5qkKWvZde6Ik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9TA4U/btqI4TxUzg8/qCkFbXR6Hv5qkKWvZde6Ik/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9TA4U/btqI4TxUzg8/qCkFbXR6Hv5qkKWvZde6Ik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9TA4U%2FbtqI4TxUzg8%2FqCkFbXR6Hv5qkKWvZde6Ik%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이번 포스팅에서는 코틀린의 타입 시스템에 대해 다루어볼 것이며, 주로 코틀린에서 Null을 다루는 방법을 주로 다루어볼 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;&lt;b&gt;널 가능성&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;p&gt;널 가능성은 NPE 오류를 피할 수 있게 돕기 위한 코틀린 타입 시스템의 특성이다. 자바 객체는 기본적으로 null을 허용하고, null로 받은 객체 이지만, 해당 객체의 모든 메서드를 호출할 수 있게 설계 되었기 때문에, 런타입에 NPE가 많이 발생한다. 하지만 코틀린은 null 문제를 런타임 시점이 아니라 컴파일 시점으로 옮김으로써, 컴파일 시점에 실행 시점에 발생할 수 있는 여러가지 문제의 가능성을 줄여준다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;&lt;b&gt;널이 될 수 있는 타입&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;p&gt;코틀린과 자바의 가장 중요한 차이는 코틀린 타입 시스템은 널이 될 수 있는 타입을 명시적으로 지원한다는 것이다. 여기서 널이 될 수 있는 타입이란, 프로그램 안의 프로퍼티나 변수에 null을 허용하게 만드는 방법이다. 여기까지 자바와 무슨 차이가 있냐 생각이 들겠지만, 아래 예제를 보면 확실히 코틀린과 자바의 null을 다루는 방법은 다르다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1600497244701&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//컴파일 에러
fun strLen(string: String?) = string.length&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;자바는 위 예제의 메서드를 정의하는데 아무런 컴파일 에러 없이 넘어갈 수 있지만, 코틀린은 Null을 허용하는 타입을 인자로 받는 함수에서 위와 같이 작성하면 컴파일 에러가 발생한다. 그말은 null이 될 수 있는 타입의 메서드 호출을 컴파일 타임에 제한하여 런타임에 NPE 여부를 사전에 차단해버리는 것이다. 또한 아래와 같은 것도 컴파일 시점에 잡아버린다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1600497366774&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun strLength(str: String) = str.length

fun main() {
    strLength(null) //컴파일 에러
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;함수의 정의에서 널이 될 수 없는 String을 인자로 받고 있으므로, str.length에서는 컴파일에러가 나지 않는다. 하지만, 이 함수를 가져다 쓸때 인자에 null을 넣으면 컴파일 에러가 발생한다. 그 이유는 널이 될 수 없는 타입에 null을 인자로 넘겼기 때문이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1600497480096&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val x: String? = null
val y: String = x&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위와 같은 코드도 허용하지 않는다. 널이 될 수 있는 타입을 널이 될 수 없는 타입에 대입이 불가능한것이다. 그러면 여기까지 널이 될 수 있는 타입으로 뭘할 수 있을까? 너무 제약이 많다고 생각이 들 수 있지만, 사실 널이 될 수 있는 타입과 null을 비교하여 null이 아닌 것을 증명하면 그 뒤로는 널이 될 수 없는 타입처럼 사용이 가능하다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1600497616424&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun strLenSafe(s: String?): Int =
        if (s != null) s.length else 0&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위 코드는 더 간결하게 표현 가능하지만, 여기서 다루지 않고 뒷 내용에서 다룬다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;&lt;b&gt;타입의 의미&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;p&gt;자바에서는 null을 다루기 위해, Optional이라는 해법이 나왔다. 하지만, 이 또한 어떠한 객체를 래핑하는 래퍼 역할이기 때문에 자칫하면 런타임의 성능이 떨어질 수 있다. 하지만 코틀린에서 널이 될 수 있는 타입이나 널이 될 수 없는 타입은 런타임에 각은 객체 타입이기 때문에 실행 시점의 성능은 똑같고, 부가 비용이 들지 않는다. 그 이유는 모든 가능성을 최대한 컴파일 타임에 잡기 때문이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;&lt;b&gt;안전한 호출 연산자: ?.&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;p&gt;코틀린이 제공하는 가장 유용한 도구 중 하나가 안전한 호출 연산자인 &lt;i&gt;&lt;b&gt;&quot;?.&quot;&lt;/b&gt;&lt;/i&gt;이다. &lt;i&gt;&lt;b&gt;&quot;?.&quot;&lt;/b&gt;&lt;/i&gt;은 null 검사와 메서드 호출을 한번의 연산으로 수행한다. 예를 들어, &lt;b&gt;&quot;str?.toUpperCase()&quot;&lt;/b&gt;는 &lt;b&gt;&quot;if (str != null) s.toUpperCase() else null&quot;&lt;/b&gt;과 같다. 다시 풀어서 이야기하면, 호출하려는 값이 null이 아니면 일반 메서드 호출처럼 작동하고 만약 호출하려는 값이 null이면 null이 결과가 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1600497988204&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;foo?.bar() -&amp;gt; foo != null -&amp;gt; foo.bar()
           -&amp;gt; foo == null -&amp;gt; null&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;여기서 유의 해야할 점은 안전한 호출의 결과 타입도 널이 될 수 있는 타입이라는 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1600498090105&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun getUpperCase(s: String?) = s?.toUpperCase()

val upperCase: String? = getUpperCase(&quot;str&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;안전한 호출은 클래스의 프로퍼티를 다룰때도 적용할 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1600498172545&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Employee(val name: String, val manager: Employee?)
fun managerName(employee: Employee): String? = employee.manager?.name&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이러한 안전한 호출은 연쇄해서 호출가능하다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1600498335033&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Address(val streetAddress: String, val zipCode: Int, val city: String, val country: String)
class Company(val name: String, val address: Address?)
class Person(val name: String, val company: Company?)
fun Person.countryName(): String {
    val country = this.company?.address?.country
    return if (country != null) country else &quot;Unknown&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;널 검사가 들어간 호출이 연달아 있는 경우를 자바 코드에서 아주 자주 볼 수 있다. 하지만 코틀린에서는 훨씬 간결하게 널 검사가 가능하다. 사실 위 코드에서 if문을 없애서 간결하게 표현이 가능하다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;&lt;b&gt;엘비스 연산자: ?:&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;p&gt;코틀린은 null 대신 사용할 디폴트 값을 지정할 때 편리하게 사용할 수 있는 연산자를 제공한다. 그 연산자는 엘비스 연산자라고 한다. 위 코드를 엘비스 연산자를 사용하여 한줄로 리팩토링해보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1600501039704&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun Person.countryName() = this.company?.address?.country ?: &quot;Unknown&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;훨씬 더 코드가 간결해졌다. 엘비스 연산자는 이항 연산자로 좌항을 계산한 값이 널인지 검사한다. 좌항 값이 널이 아니라면 좌항값을 그대로 리턴하고, 만약 좌항값이 널이면 우항 값으로 리턴한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1600501111232&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;foo ?: bar -&amp;gt; foo != null -&amp;gt; foo
           -&amp;gt; foo == null -&amp;gt; bar&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위 리팩토링한 코드 예제와 같이 엘비스 연산자를 객체가 널인 경우 널을 반환하는 안전한 호출 연산자와 함께 사용해서 객체가 널인 경우 디폴트 값을 반환하도록 많이 사용한다. 그리고 코틀린에서는 return&amp;amp;throw 등의 연산도 식이기 때문에, 엘비스 연산자 우항에 throw 식을 넣어 null 일 경우 예외를 발생시키는 등의 로직을 만들 수 있다. 이러한 패턴으로 함수의 전제 조건을 검사하는 경우 아주 유용한 연산자가 될 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1600501386791&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Address(val streetAddress: String, val zipCode: Int, val city: String, val country: String)
class Company(val name: String, val address: Address?)
class Person(val name: String, val company: Company?)

fun printShippingLabel(person: Person) {
    val address = person.company?.address ?: throw IllegalArgumentException(&quot;empty address&quot;)
    with (address) {
        println(streetAddress)
        println(&quot;$zipCode $city, $country&quot;)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;안전한 호출 연사자, 엘비스 연산자, with 함수를 이용하여 위와 같은 코드를 만들 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;&lt;b&gt;안전한 캐스트: as?&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;p&gt;코틀린은 자바와 같이 타입 캐스트시 대상 타입으로 캐스팅을 할 수 없을 경우, ClassCastException이 발생한다. 하지만 역시 코틀린에서는 컴파일 타임에 최대한 해당 예외 발생을 줄여주기 위해 안전한 캐스트 연산자인 &lt;b&gt;&quot;as?&quot;&amp;nbsp;&lt;/b&gt;를 지원한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;as?&lt;/b&gt;연산자는 어떤 값을 지정한 타입으로 캐스팅하는데, 캐스팅할 수 없는 타입이라면 null을 반환한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1600501652460&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;foo as? Type -&amp;gt; foo is Type -&amp;gt; foo as Type
             -&amp;gt; foo !is Type -&amp;gt; null&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;안전한 캐스팅을 이용할때는 보통 엘비스 연산자를 같이 사용하는 경우가 많다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1600501795774&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Person(val name: String, val company: Company?) {
    override fun equals(other: Any?): Boolean {
        val otherPerson = other as? Person ?: return false
        
        return otherPerson.name == this.name
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위 예제는 other가 Person 타입이 아닐 경우, 바로 equals 함수의 값을 false로 리턴한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;&lt;b&gt;let 함수&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;p&gt;let 함수를 이용하면 널이 될 수 있는 식을 쉽게 다룰 수 있다. let 함수를 안전한 호출 연산자와 함께 사용하면 원하는 식을 평가해서 결과가 널인지 검사한 다음에 그 결과를 변수에 넣는 작업을 간단한 식을 사용해 한번에 처리할 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1600506624764&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun sendEmailTo(email: String) {
    println(&quot;send email to $email&quot;)
}

fun main() {
    val email: String? = null
    if (email != null) sendEmailTo(email)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위 예제를 보면 sendEmailTo 함수를 호출하기전에 인자 값이 널인지를 체크해야지만 컴파일 에러가 나지 않는다. 하지만 let 함수를 사용해 위와 같은 로직을 더 간결하게 표현할 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1600506672361&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main() {
    val email: String? = null
    email?.let { sendEmailTo(it) }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;email의 안전한 호출 구문을 호출했고, email이 널이 아닐 때만, let의 수신 객체로 들어가게 된다. 즉, let 함수 안의 it이 널이 될 수 없는 email이 되는 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1600506777493&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;foo?.let { -&amp;gt; foo != null -&amp;gt; let함수가 수행되고, it은 널이 아니다.
	..it.. -&amp;gt; foo == null -&amp;gt; 아무일도 일어나지 않고, null이 담긴다.
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;let 함수는 긴 식이 있고, 그 값이 널이 아닐 때 수행해야하는 로직이 있을 때 아주 유용하다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;&lt;b&gt;Lazy init&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;p&gt;코틀린에서 널이 될 수 없는 타입의 프로퍼티는 반드시 생성자 안에서 초기화하지 않고, 특별한 메서드 안에서 초기화할 수는 없다. 코틀린에서는 일반적으로 생성자에서 모든 프로퍼티를 초기화해야 한다. 게다가 널이 될 수 없는 타입의 프로퍼티는 널이 아닌 값으로 초기화해야한다. 그렇다고 널이 될 수 있는 프로퍼티에 null를 할당하고, 생성자가 아닌 함수에서 초기화를 해버리면 해당 프로퍼티를 사용하는 곳에서 항상 null 체크를 하는 코드를 넣어줘야 한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1600520478600&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class MyService {
    fun performAction(): String = &quot;foo&quot;
}

class MyTest {
    private var myService: MyService = null
    
    @Before
    fun setUp() {
        myService = MyService()
    }
    
    @Test
    fun testAction() {
        Assert.assertEquals(&quot;foo&quot;, myService!!.performAction())
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위와 같은 예제와 비슷하게 스프링 프레임워크와 코틀린을 같이 사용하는 경우 DI를 위해 생성자 레벨에서 초기화를 하지 못하는 경우가 종종있다. 이러한 문제 해결을 위해 코틀린에서는 &lt;i&gt;&lt;b&gt;lazy init&lt;/b&gt;&lt;/i&gt;(지연 초기화)를 제공한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1600520578645&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class MyTest {
    private lateinit var myService: MyService

    @Before
    fun setUp() {
        myService = MyService()
    }

    @Test
    fun testAction() {
        Assert.assertEquals(&quot;foo&quot;, myService.performAction())
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;지연 초기화를 사용하기 위해서는 프로퍼티가 항상 var여야 한다. val 프로퍼티는 final 필드로 컴파일되며, 생성자에서 반드시 초기화해야한다. 만약 lateinit으로 선언한 프로퍼티를 초기화하지 않고 어딘가에서 사용한다면 어떻게 될까? 그럴 경우에는 코틀린은 런타임 시점에 아직 초기화가 되지 않았다라는 에러를 발생시켜준다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;&lt;b&gt;널이 될 수 있는 타입 확장 함수&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;p&gt;널이 될 수 있는 타입의 확장함수를 정의하면 null에 대한 대비를 조금더 우아하게 할 수 있다. String 클래스의 함수를 예제로 살펴보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1600521547028&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@kotlin.internal.InlineOnly
public inline fun CharSequence?.isNullOrBlank(): Boolean {
    contract {
        returns(false) implies (this@isNullOrBlank != null)
    }

    return this == null || this.isBlank()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;수신 객체가 null임을 체크함으로써 String 객체가 null 일 경우를 대비할 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;&lt;b&gt;타입 파라미터의 널 가능성&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;p&gt;코틀린에서 타입 파라미터는 기본적으로 널이 될 수 있는 타입이다. 즉, Any? 타입이 되는 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1600522370962&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun &amp;lt;T&amp;gt; printObject(t: T) {
    println(t?.toString())
}

fun &amp;lt;T: Any&amp;gt; printObject(t: T) {
    println(t.toString())
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;하지만 위 예제처럼 타입 파라미터의 upper bound를 지정하여 널이 될 수 없는 타입 파라미터로 정의할 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;여기까지 코틀린에 타입 시스템에 대해 간단히 다루어 보았다. 마지막으로, 코틀린에서 자바 API를 이용하며, 상속 혹은 인터페이스를 구현할때, 보통 메서드를 오버라이드한다. 그때 자바 상위타입 메서드 선언의 인자의 타입을 널이 허용되는 타입으로 봐야할까 널이 허용되지 않는 타입으로 봐야할까? 그것은 코틀린에서 구현하는 사람이 정해야한다. 그 말은 둘다 가능하다는 말이다. 실제 그 상위 인터페이스 혹은 클래스의 용도를 잘 분석해 널을 허용하는 인자로 볼지 널을 허용하지 않는 인자로 볼지 잘 결정하고 구현해야한다.&lt;/p&gt;</description>
      <category>프로그래밍언어/Kotlin</category>
      <category>?.</category>
      <category>?:</category>
      <category>jvm</category>
      <category>kotlin</category>
      <category>lazyinit</category>
      <category>Let</category>
      <category>엘비스연산자</category>
      <category>코틀린</category>
      <category>코틀린 null</category>
      <author>여성게</author>
      <guid isPermaLink="true">https://coding-start.tistory.com/399</guid>
      <comments>https://coding-start.tistory.com/399#entry399comment</comments>
      <pubDate>Sat, 19 Sep 2020 19:56:52 +0900</pubDate>
    </item>
    <item>
      <title>Kotlin - with와 apply란? (수신 객체 지정 람다)</title>
      <link>https://coding-start.tistory.com/398</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bNCSB4/btqI5TEaCRq/f3Llt2HiepPs1d9FMHnDe1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bNCSB4/btqI5TEaCRq/f3Llt2HiepPs1d9FMHnDe1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bNCSB4/btqI5TEaCRq/f3Llt2HiepPs1d9FMHnDe1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbNCSB4%2FbtqI5TEaCRq%2Ff3Llt2HiepPs1d9FMHnDe1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;오늘 다루어볼 내용은 코틀린의 with와 apply 함수이다. 바로 예제로 들어간다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;&lt;b&gt;with 함수&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;pre id=&quot;code_1600493648026&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun alphabet(): String {
    val result = StringBuilder()
    for (letter in 'A'..'Z') {
        result.append(letter)
    }
    result.append(&quot;\nNow I Know the alphabet!&quot;)
    return result.toString()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위 코드는 알파벳을 출력해주는 함수이다. 뭔가 StringBuilder를 생성하여 특정 변수에 담고, 해당 변수를 사용해 함수를 호출하고 뭔가 군더더기가 많이 붙어있는 느낌이다. 해당 코드를 with 함수를 통해 리팩토링이 가능하다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1600493723839&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun alphabet() = with(StringBuilder()) {
    for (letter in 'A'..'Z') {
        append(letter)
    }
    append(&quot;\nNow I Know the alphabet!&quot;)
    toString()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;코드가 뭔가 훨씬 깔끔해졌다. 눈에 띄는 것이 with 함수이다. 간단하게 with 함수는 하나의 인자를 받는 함수 같지만, 실제로는 2개의 인자를 받는 함수이다.&lt;/p&gt;
&lt;p&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&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;람다에서는 첫번째 인자로 받은 StringBuilder의 인스턴스가 수신객체가 되기때문에 람다안에서 마치 StringBuilder의 내부 함수처럼 사용이 가능하다.(this 키워드로 참조가 가능하지만, 겹치는 함수 이름이 없어 this를 생략하였다.) 그리고 해당 with 함수의 반환값은 람다의 반환값이 된다. with 함수의 내부를 살펴보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1600493919038&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@kotlin.internal.InlineOnly
public inline fun &amp;lt;T, R&amp;gt; with(receiver: T, block: T.() -&amp;gt; R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return receiver.block()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;우선 with 함수는 T 타입을 인자로 받고 R타입을 리턴하는 함수이다. 내부 코드를 보면 조금 특이하다. 두번째 인자로 받은 람다가 마치 첫번째로 받은 인자 타입이 수신객체인 확장함수처럼 동작을 하고 있는 것이다. 이런 with 함수를 이용하면 특정 상황에서 훨씬 깔끔한 코드가 나올 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;&lt;b&gt;apply함수&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;p&gt;with 함수는 람다의 반환값이 with의 반환값이 된다. 하지만, 람다는 람다대로 수행하고 실제 수신 객체가 함수의 리턴값이 되길 바랄때가 있는데 이럴때 apply 함수를 이용한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1600494101569&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun alphabetUsingApply() = StringBuilder().apply {
    for (letter in 'A'..'Z') {
        append(letter)
    }
    append(&quot;\nNow I Know the alphabet!&quot;)
}.toString()&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위 예제는 with 예제와는 다르게, 우선 람다대로 수행하고 리턴값이 수신 객체의 인스턴스 자기 자신이 되는 것이다.&lt;/p&gt;
&lt;p&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;with : 람다의 마지막 식(결과)가 with함수의 반환값이 된다. 즉, with의 두번째 인자 람다의 반환은 특정 객체를 리턴해야하는 것이다.&lt;/li&gt;
&lt;li&gt;apply : 람다는 람다대로 수행하고, 수신 객체 자기자신을 반환한다. 즉, apply의 인자인 람다의 반환은 Unit(void)이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;apply의 함수 내부를 살펴보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1600494211169&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@kotlin.internal.InlineOnly
public inline fun &amp;lt;T&amp;gt; T.apply(block: T.() -&amp;gt; Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block()
    return this
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;apply 함수 내부를 보면, apply를 호출한 인스턴스 타입이 수신 객체인 확장 함수 람다를 인자로 받는다. 해당 람다의 반환은 Unit이다. 실제 코드 내부에서는 해당 람다를 수행하고 자기자신(this)를 리턴하고 있다. 실제 apply를 활용하여 코틀린이 구현한 유용한 함수의 예제로 아래와 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1600494406410&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * buildString은 StringBuilder를 만들어주는 것과 toString을 호출하는것을 대신해준다.
 */
fun alphabetUsingBuildString() = buildString {
    for (letter in 'A'..'Z') {
        append(letter)
    }
    append(&quot;\nNow I Know the alphabet!&quot;)
}

@kotlin.internal.InlineOnly
public inline fun buildString(builderAction: StringBuilder.() -&amp;gt; Unit): String =
    StringBuilder().apply(builderAction).toString()&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;여기까지 코틀린의 with와 apply에 대해 간단히 다루어보았다.&lt;/p&gt;</description>
      <category>프로그래밍언어/Kotlin</category>
      <category>apply</category>
      <category>java</category>
      <category>jvm</category>
      <category>kotlin</category>
      <category>with</category>
      <category>수신객체</category>
      <category>코틀린</category>
      <category>확장함수</category>
      <author>여성게</author>
      <guid isPermaLink="true">https://coding-start.tistory.com/398</guid>
      <comments>https://coding-start.tistory.com/398#entry398comment</comments>
      <pubDate>Sat, 19 Sep 2020 14:47:30 +0900</pubDate>
    </item>
    <item>
      <title>Kotlin - 코틀린의 클래스, 객체, 인터페이스</title>
      <link>https://coding-start.tistory.com/397</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xLIxQ/btqIVHczJhw/9pIo8qbeKedL90FM3Jj3W1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xLIxQ/btqIVHczJhw/9pIo8qbeKedL90FM3Jj3W1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xLIxQ/btqIVHczJhw/9pIo8qbeKedL90FM3Jj3W1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxLIxQ%2FbtqIVHczJhw%2F9pIo8qbeKedL90FM3Jj3W1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이전까지 간단하게 코틀린에 대한 간략한 문법들을 다루어봤는데, 이번 포스팅은 코틀린의 클래스, 객체, 인터페이스에 대해 다루어본다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;&lt;b&gt;인터페이스&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;p&gt;자바의 인터페이스와 크게 다르지 않다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1600406634080&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface SampleInterface1 {
    val property: String
    fun method1()
    fun method3() = println(&quot;method2&quot;)
}

interface SampleInterface2 {
    fun method2()
    fun method3() = println(&quot;method2&quot;)
}

class SampleImpl(): SampleInterface1, SampleInterface2 {
    override val property: String
        get() = TODO(&quot;Not yet implemented&quot;)

    override fun method1() {
        TODO(&quot;Not yet implemented&quot;)
    }

    override fun method3() {
        super&amp;lt;SampleInterface2&amp;gt;.method3()
    }

    override fun method2() {
        TODO(&quot;Not yet implemented&quot;)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;자바처럼 여러 인터페이스를 한 클래스에서 구현할 수도 있다. 또한 자바의 default 메서드처럼 코틀린에서는 그냥 함수 구현체를 정의하면 그게 default 함수가 된다. 하지만 유의할점은, 구현하고 있는 인터페이스 두개에서 같은 이름의 함수가 있다면, 구현하고 있는 하위 함수에서 오버라이드 해주어야한다.(혹은 특정 상위 인터페이스의 구현을 사용한다고 명시할 수도 있다.)&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;자바는 @Override 어노테이션을 사용하지만 코틀린은 override 키워드를 사용한다. 마지막으로 자바와는 다르게 코틀린은 인터페이스에서 프로퍼티를 선언할 수 있다. 만약 인터페이스에 프로퍼티가 선언되어 있다면, 구현체에서 Getter를 오버라이드 해주면 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;&lt;b&gt;코틀린의 열린 클래스(클래스상속)&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;p&gt;자바에서는 기본적으로 클래스를 상속하면 다른 클래스에서 상속을 받을 수 있는 구조이다(public, not final) 하지만, 코틀린은 기본적으로 클래스가 모두 final 이기때문에 상속을 받을 수 없다. 하지만, 일반 클래스도 상속을 허용하기 위해 열린 클래스를 정의할 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1600407360424&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;open class SampleOpen(val value: String): SampleInterface1 {
    override val property: String
        get() = TODO(&quot;Not yet implemented&quot;)

    fun disable() {}
    open fun animate() {}
    override fun method1() {
        TODO(&quot;Not yet implemented&quot;)
    }
}

class SampleOpenImpl(val key: String, value: String): SampleOpen(value) {
    override fun animate() {
        super.animate()
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;open이라는 키워드를 붙여 상속이 가능한 형태의 클래스를 만들 수 있다. 하지만 자바와는 조금 문법이 다르게 하위 클래스에서 상위 클래스의 생성자 인자만큼 괄호안에 프로퍼티를 넣어줘야하는데, 그 프로퍼티를 생성자 시점에 넘겨줘야한다.(자바와 비슷) 하지만 자바와는 다르게 하위클래스의 생성자 프로퍼티 명을 그대로 상위 클래스 생성자 아규먼트로 넣어주면 된다. 또한 open class는 구현체 함수도 들어갈 수 있고, 상속을 강제하기 위해 open 키워드를 붙인 함수를 정의할 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;&lt;b&gt;확장함수의 접근제한자&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;p&gt;코틀린에서는 확장함수라는 아주 편한 문법이 존재한다. 하지만 하나 유의해야할 점이 확장함수를 정의할때 수신 객체 타입의 접근제한자의 레벨과 같거나 낮아야한다. 그말은 코틀린의 internal(같은 모듈에서만 사용가능)으로 수신객체의 접근제한자로 정의해놓았다면, 확장 함수는 반드시 internal 제한과 같거나 낮아야한다.(public으로는 internal 수신 객체의 확장함수를 정의할 수 없다.)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1600407650685&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;internal class SampleInternal {
    
}

internal fun SampleInternal.expendFunction() {} -&amp;gt; ok
fun SampleInternal.expendFunction() {} -&amp;gt; 컴파일 에러
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;&lt;b&gt;sealed 클래스&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;p&gt;아래와 같은 코드가 있다고 가정하자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1600408137741&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface InterfaceExam {}
class InterfaceImpl1: InterfaceExam
class InterfaceImpl2: InterfaceExam
class InterfaceImpl3: InterfaceExam

fun whenExam(obj: InterfaceExam) = when (obj) {
    is InterfaceImpl1 -&amp;gt; println(&quot;interfaceImpl1&quot;)
    is InterfaceImpl2 -&amp;gt; println(&quot;interfaceImpl2&quot;)
    is InterfaceImpl3 -&amp;gt; println(&quot;interfaceImpl3&quot;)
    else -&amp;gt; println(&quot;els&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;when 식에서 인터페이스 구현체의 타입을 분기하는 로직인데, 실제로 모든 구현체를 다 명시했음에도 else 구문이 들어가야한다. 하지만 코틀린에서 sealed 타입을 구현함으로써 else문을 명시하지 않아도된다.(sealed로 상위 타입을 명시함으로써 when 식에 모든 타입이 들어왔으니 else 구문이 오지 않을 것이라라고 강제할 수 있어 else 구문이 필요없다.)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1600408250028&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sealed class SampleSealed {
    open fun method() {}
}

class SampleSealedImpl1: SampleSealed() {
    override fun method() {
        super.method()
    }
}
class SampleSealedImpl2: SampleSealed() {
    override fun method() {
        super.method()
    }
}

class SampleSealedImpl3: SampleSealed() {
    override fun method() {
        super.method()
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;&lt;b&gt;클래스 생성자&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;p&gt;코틀린에서 주 생성자는 자바보다 간결하게 정의가능하다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1600408358703&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class User1(val name: String)

#java
public final class User1 {
	private final String name;
    
    public User1(String name) {
    	this.name = name
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위 정의는 모든 필드가 final이고, 그 필드를 모든 필드를 인자로 받는 생성자로 초기화하는 것과 같은 것이다. 위와 같은 생성자는 코틀린에서 펼친 표현으로는 아래와 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1600408461404&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class User2 constructor(name: String){
    val name: String
    init {
        this.name = name
    }
}

class User3(name: String){
    val name: String = name
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;복잡한 초기화 로직을 적용해야하는 클래스는 주 생성자말고 부 생성자를 여러개 정의할 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1600408524338&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Secretive private constructor() {}

open class View {
    constructor(title: String): this(title, &quot;&quot;)
    constructor(title: String, content: String) {}
}

class MyButton: View {
    constructor(title: String): super(title){

    }

    constructor(title: String, content: String): super(title, content) {

    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;&lt;b&gt;인터페이스 프로퍼티 오버라이드&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;p&gt;코틀린의 인터페이스에서는 프로퍼티를 선언할 수 있는데, 그 예로 아래와 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1600408561282&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface Book {
    val name: String
}

class EBook1(override val name: String): Book
class EBook2: Book {
    override val name: String
        get() = TODO(&quot;Not yet implemented&quot;)
}
class EBook3(val category: String): Book {
    override val name = getBookName(category)
    private fun getBookName(category: String) = &quot;ebook&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;&lt;b&gt;데이터 클래스&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;p&gt;자바의 레코드 클래스와 비슷하게 코틀린에는 데이터 클래스가 있다. 데이터 클래스는 코틀린에서 필요한 함수를 모두 숨겨서 구현해주고 있다.(&lt;u&gt;&lt;i&gt;&lt;b&gt; equal(), hashCode(), toString()&lt;/b&gt; , &lt;b&gt;copy()&lt;/b&gt;&lt;/i&gt;&lt;/u&gt;)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1600408640428&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;data class BookDto(val name: String, val price: Int) {}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;&lt;b&gt;위임클래스&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;p&gt;코틀린에서는 delegate pattern을 아주 쉽게 구현할 수 있는 수단이 존재한다. 만약 자바에서 HashMap을 상속하여 커스텀한 로직을 넣은 클래스를 만든다면 여러 불필요한 코드가 많고, 실제 상위 클래스의 메서드에 의존해야한다라는 단점이 있는데, 코틀린은 이를 아주 우하하게 해결해주었다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1600408745650&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * 자바와 달리 데코레이터 패턴을 아주 쉽게 구현할 수 있다.
 * 보통 특정 클래스를 상속할때 상위 클래스의 기능이 변경되면, 하위 클래스의 동작에도 문제가 생기는데
 * 아래처럼 위임 관계를 만들어주면 실제 상위 클래스의 구현에 의존하지 않고, 위임하기때문에 상위클래스 변경에
 * 안전하다.
 */
class DelegatingCollection&amp;lt;T&amp;gt;(
        private val innerList: Collection&amp;lt;T&amp;gt; = ArrayList&amp;lt;T&amp;gt;()
): Collection&amp;lt;T&amp;gt; by innerList {

}

class BookNamePriceMap(
        private val map: Map&amp;lt;String, Int&amp;gt; = hashMapOf()
): Map&amp;lt;String, Int&amp;gt; by map {

}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;만약 오버라이드해야하는 함수가 있다면 오버라이드하면 되고, 나머지는 &lt;b&gt;&quot;by map&quot;&lt;/b&gt;이라는 키워드로 인해 자동으로 상위 클래스의 함수로 위임해준다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;&lt;b&gt;객체의 선언과 생성을 동시에 해주는 Object&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;p&gt;코틀린에는 자바와는 다르게 object라는 타입이 존재한다. 간단하게는 싱글톤을 만들어주고, 마치 static한 유틸클래스처럼 이용가능한 타입이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1600408883024&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;object Payroll {
    const val companyName = &quot;company&quot;
    init {
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;선언과 생성이 동시에 이루어지기 때문에, 상태값을 담은 프로퍼티 선언을 하지 않고 보통은 상수정도의 프로퍼티를 갖는다. object 타입에 함수를 정의하면 Payroll.method() 처럼 정적인 클래스처럼 이용가능하다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;&lt;b&gt;동반객체(companion object)&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;p&gt;특정 클래스의 생성을 담당할 팩토리 클래스 등을 만들때 아주 좋은 코틀린의 기능이 있다. 또한 마치 확장함수와 같은 기능을 제공하기도 하는데 아래 예제를 살펴보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1600409126620&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class OuterClass private constructor(val name: String) {
    //내부 객체선언이 동반 객체선언이라면, 내부 객체 선언의 private 멤버 혹은 함수 접근이 가능하다.
    private fun method2() = method()
    private fun method3() = value
    
    companion object OuterClassFactory {
        private const val value = &quot;&quot;
        
        fun newOuterClass() = OuterClass(&quot;outerName&quot;)
        
        private fun method() = println(&quot;&quot;)
        
        fun companionMethod() = println(&quot;&quot;)
    }
}

OuterClass.newOuterClass()
OuterClass.companionMethod()&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;자바와는 달리 코틀린의 내부클래스는 외부 클래스를 참조하지 않는다. 또한 companion 키워드를 넣어주면, 바깥 클래스에서 내부 객체의 private한 멤버도 참조가능하다. 그렇다면 그냥 object로 선언된 내부 객체는 어떠할까&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1600409205326&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class OuterClass private constructor(val name: String) {
    private fun method() = println(&quot;private method&quot;)
    //내부 객체선언이 일반 객체선언이라면, 내부 객체 선언의 private 멤버 혹은 함수 접근이 안된다.
    private fun method2() = OuterClassFactory.method() //컴파일에러
    
    object OuterClassFactory {
        fun newOuterClass() = OuterClass(&quot;outerName&quot;)
        
        private fun method() = println(&quot;&quot;)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;일반 object 내부 객체로 정의되어있다면, 외부 클래스에서 내부 객체의 private 멤버에 접근할 수 없다. 이를 잘 활용하면 팩토리 클래스를 따로 분리할 필요없이 클래스 내부에 정의가 가능해지는 것이다. 또한 이와 비슷한 느낌의 로직도 한곳에 다모이게 되어 관리가 쉬워질 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;여기까지 코틀린의 클래스, 인터페이스, 객체에 대해 간단히 다루어보았다.&lt;/p&gt;</description>
      <category>프로그래밍언어/Kotlin</category>
      <category>Companion</category>
      <category>java</category>
      <category>jvm</category>
      <category>kotlin</category>
      <category>override</category>
      <category>sealed</category>
      <category>동반객체</category>
      <category>위임 클래스</category>
      <category>자바</category>
      <category>코틀린</category>
      <author>여성게</author>
      <guid isPermaLink="true">https://coding-start.tistory.com/397</guid>
      <comments>https://coding-start.tistory.com/397#entry397comment</comments>
      <pubDate>Thu, 17 Sep 2020 10:09:59 +0900</pubDate>
    </item>
    <item>
      <title>Kotlin - 함수 정의와 호출</title>
      <link>https://coding-start.tistory.com/395</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kwMUl/btqIPQU47Sv/qSjWL8K0SUDKM3h6MXJ0K0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kwMUl/btqIPQU47Sv/qSjWL8K0SUDKM3h6MXJ0K0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kwMUl/btqIPQU47Sv/qSjWL8K0SUDKM3h6MXJ0K0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkwMUl%2FbtqIPQU47Sv%2FqSjWL8K0SUDKM3h6MXJ0K0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;오늘은 코틀린의 함수 정의와 호출에 대해 다루어 본다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;&lt;b&gt;컬렉션 객체 만들기&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;pre id=&quot;code_1600220564002&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun createHashSet() = hashSetOf(1, 7, 53)

fun createArrayList() = arrayListOf(1, 7, 53)

fun createHashMap() = hashMapOf(1 to &quot;one&quot;, 7 to &quot;seven&quot;, 53 to &quot;fifty-three&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;기본적으로 코틀린은 컬렉션을 만드는 함수를 기본 라이브러리에 내장이 되어 있다. 또한 마지막에 hashMap을 만드는 함수 안에 &quot;to&quot;라는 키워드가 있는데, 사실 키워드가 아니고 일반 함수이다. 이점은 뒤에서 자세히 설명한다. 또한 생성하는 컬렉션 객체는 코틀린만의 컬렉션 객체가 아니고, 자바의 컬렉션 객체를 생성한다. 하지만 자바에서 제공하고 있는 기능보다 더 유용한 기능들을 제공한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1600220822223&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun collectionOperate() {
    val list = listOf(&quot;one&quot;, &quot;two&quot;, &quot;three&quot;)
    //리스트의 마지막 원소를 가져온다.
    println(list.last())
    
    val numbers = setOf(1, 14, 2)
    //숫자로 이루어진 컬렉션(Set)에서 가장 큰 값을 가져온다.
    println(numbers.max())
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;&lt;b&gt;디폴트 파라미터 값&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;pre id=&quot;code_1600248158027&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun &amp;lt;T&amp;gt; defaultParamValue(collection: Collection&amp;lt;T&amp;gt;, separator: String = &quot;, &quot;, prefix: String = &quot;&quot;, postfix: String = &quot;&quot;): String {
    val result = StringBuilder(prefix)
    for ((index, elem) in collection.withIndex()) {
        if (index &amp;gt; 0) result.append(separator)
        result.append(elem)
    }
    result.append(postfix)
    return result.toString()
}

fun main() {
    println(defaultParamValue(list))
}

-&amp;gt;
1, 2, 3&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;만약 이름있는 파라미터를 사용하면, 함수 호출시 파라미터 순서를 지정할 필요가 없어진다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1600248257606&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;println(defaultParamValue(list, postfix = &quot;#&quot;, prefix = &quot;;&quot;))&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이러면, 순서와 상관없이 파라미터를 지정할 수 있고, seperate라는 파라미터도 디폴트 값이 적용된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;&lt;b&gt;최상위 함수와 프로퍼티&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;pre id=&quot;code_1600248873441&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun topLevelFunction() {
    println(&quot;top level function&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;코틀린에서 클래스 밖에 함수를 정의하면 마치 자바의 static method와 같이 작동한다. 하지만 자바와는 다르게 클래스명을 임포트 할 필요 없이 함수명을 바로 임포트해서 사용할 수 있다. 어떻게 이렇게 실행 될 수 있을까? 이는 바로 JVM이 컴파일할때 새로운 클래스를 정의해주기 때문이다.(file이름을 기반으로 클래스 생성) 실제로 자바에서 해당 함수를 가져다 쓰려면 컴파일러가 만들어준 클래스명을 명시해서 사용해야 한다. ex) JoinKt.joinToString(..)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;함수와 마찬가지로 프로퍼티도 파일의 최상위 수준에 놓을 수 있다. 기본적으로 최상위 프로퍼티도 다른 모든 프로퍼티처럼 접근자 메서드를 통해 자바 코드에 노출된다. 겉으론 상수처럼 보이지만, 실제로는 게터를 이용해야한다니 조금 이상하다. 그렇게 때문에 더 자연스럽게 자바처럼 public static final 같은 구문을 넣어주는 것이 자연스럽기 때문에 최상위 프로퍼티는 보통 아래와 같이 정의한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1600249127831&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//Java : public static final String UNIX_LINE_SEPARATOR = &quot;\n&quot;
const val UNIX_LINE_SEPARATOR = &quot;\n&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;&lt;b&gt;확장 함수와 확장 프로퍼티&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;p&gt;기존 자바 API를 이용해 확장함수 및 확장 프로퍼티를 정의하는 것이 가능하다. 그 말은 우리가 지금까지 사용했던 setOf(), arrayListOf()등을 호출할 수 있었던 이유다. 다음 예제를 보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1600251457309&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun String.lastChar(): Char = this.get(this.length - 1)

fun main() {
    println(&quot;Kotlin&quot;.lastChar())
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;함수명 앞에 &quot;String.&quot; 을 붙여서 확장할 클래스명을 넣어준다. 이것을 &lt;u&gt;&lt;i&gt;&lt;b&gt;수신객체타입&lt;/b&gt;&lt;/i&gt;&lt;/u&gt;이라 부르고, 함수 내용에 &quot;this&quot; 키워드로 수신객체 타입을 참조할 수 있다. 이 this 키워드로 참조하는 객체를 &lt;u&gt;&lt;i&gt;&lt;b&gt;수신객체(위에서 &quot;Kotlin&quot;이라는 문자열이 수신객체가 된다.)&lt;/b&gt;&lt;/i&gt;&lt;/u&gt;라고 부른다. 여기서 기억할 것은 확장 함수가 캡슐화를 깨지않는다는 것이다. 그 이유는 수신 객체의 private 메서드 같은 것은 접근할 수 없기 때문이다. 여기서 만약, 다른 패키지의 많은 확장함수를 임포트해서 사용하는데 함수의 시그니쳐 등이 모두 동일하면 충돌하는 현상이 발생되는데 이런 경우는 아래와 같이 &quot;as&quot; 키워드로 확장함수의 별칭을 지어줄 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1600251858219&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import com.example.kotlin.ch_3.lastChar as last

fun main() {
    println(&quot;Kotlin&quot;.last())
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;&lt;b&gt;확장함수로 유틸리티 클래스 만들기&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;p&gt;기존 자바 API 혹은 코틀린 클래스를 수신객체로 확장함수를 사용해 유틸클래스를 만들면 유용하다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1600253376315&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun &amp;lt;T&amp;gt; Collection&amp;lt;T&amp;gt;.joinToString(seperate: String): String {
    val result = StringBuilder()
    for ((i, elem) in this.withIndex()) {
        if (i &amp;gt; 0) result.append(seperate)
        result.append(elem)
    }
    return result.toString()
}

fun main() {
    val list = listOf(1,2,3)
    println(list.joinToString(&quot;,&quot;))
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이렇게 제네릭 타입으로 확장함수를 만들수 있고, 특정 타입으로 제한하여 확장함수를 만들 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1600253516477&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main() {
    val list = listOf(1,2,3)
    println(list.join2(&quot;,&quot;))
}

fun Collection&amp;lt;String&amp;gt;.join2(seperate: String): String {
    val result = StringBuilder()
    for ((i, elem) in this.withIndex()) {
        if (i &amp;gt; 0) result.append(seperate)
        result.append(elem)
    }
    return result.toString()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;만약 String을 타입으로 받는 확장함수를 만들었는데, Int를 요소로 가지는 리스트에 대해 확장함수를 호출하면 컴파일 에러가 발생한다. 마지막으로 &quot;&lt;i&gt;&lt;b&gt;확장함수는 함수 오버라이드가 불가능하다.&quot; 또한 확장함수와 그 수신객체의 함수의 시그니쳐가 동일하면 수신객체의 멤버 함수를 우선시 한다.&lt;/b&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;&lt;b&gt;상속, 인터페이스 구현관계의 확장함수&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;p&gt;아래와 같은 예제가 있다고 가정하자. 코틀린에서 클래스는 final 클래스와 같으므로, 그냥 상속이 안된다. 그렇기 때문에 class 앞에 open 키워드를 넣어준다. 이는 함수 레벨도 동일하다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1600254100232&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;open class View {
    open fun click() = println(&quot;View clicked&quot;)
}

class Button: View() {
    override fun click() {
        println(&quot;Button clicked&quot;)
    }
}

fun main() {
    val view: View = Button()
    view.click()
}

-&amp;gt;
Button clicked&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위 예제는 상속을 예제로 코드를 작성하였는데, 결과로는 Button의 오버라이드된 메서드의 결과가 출력된다. 그렇다면 아래 예제는 어떨까?&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1600254190291&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun View.showOff() = println(&quot;I'm a view !&quot;)
fun Button.showOff() = println(&quot;I'm a button !&quot;)

fun main() {
    val view: View = Button()
    view.showOff()
}

-&amp;gt;
I'm a view !&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위 결과는 상속과는 다르게 동작한다. 실제 변수에 담긴 타입은 Button()이지만, 결과는 View의 확장함수로 작동한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;&lt;b&gt;가변 인자 함수&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;pre id=&quot;code_1600255917953&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun &amp;lt;T&amp;gt; listOf(vararg values: T): List&amp;lt;T&amp;gt; {...}

fun main(args: Array&amp;lt;String&amp;gt;) {
    listOf(&quot;abc&quot;, *args)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위 listOf는 &quot;vararg&quot;라는 키워드로 가변인자를 받을 수 있는 파라미터를 정의했다. 자바에서는 &quot;...&quot;으로 배열인 가변인자를 받을 수 있지만, 코틀린은 vararg라는 키워드를 이용한다. 그리고 또 다른 자바와의 차이점은 배열자체를 인자로 넘기는 것이 아니고, 배열 변수 앞에 &quot;*&quot;를 붙여서 명시적으로 배열을 풀어서 넣는 것이다. 기술적으로는 스프레드 연산자가 그런 작업을 해준다. 그리고 또하나의 차이점은 자바와는 달리 배열 혹은 인자를 나열하는 것이 아니라, 인자+배열로 여러 값을 함께 인자로 넘길 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;&lt;b&gt;값의 쌍을 다루기&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;p&gt;이전에&amp;nbsp; Map 오브젝트를 만드는 예제에서 &quot;to&quot;라는 키워드를 사용한 적이 있었다. 여기서 &quot;to&quot;는 키워드가 아니고, 함수라고 이야기를 했는데, 이 함수를 바로 중위함수라는 특별한 방식으로 동작하는 함수이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1600294134661&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun pair() {
    val pair = 1 to &quot;one&quot;
    println(pair)
}

fun main() {
    pair()
}

-&amp;gt;
(1, one)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;여기서 to, 중위함수는 파라미터가 유일한 함수를 이렇게 호출할 수 있게 되는 것이다. 사실은 &quot;1.to(&quot;one&quot;)&quot;과 같은 구문이며, 실제로는 to는 파라미터가 하나로 유일한 함수인 것이다. 이것을 실제로 예제로 구현해보면 아래와 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1600294235642&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;infix fun Int.pair(v: String): Pair&amp;lt;Int, String&amp;gt; {
    return Pair(this, v)
}

fun main() {
    1 pair &quot;one&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;함수의 정의를 보면, Int의 확장함수로 선언했으며 실제로 파라미터는 유일하게 하나만 받고 있다. 이러한 함수를 중위호출에 사용하려면 &quot;infix&quot;라는 키워드를 선언하면 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;&lt;b&gt;구조 분해 선언&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;pre id=&quot;code_1600294599083&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main() {
    val (left, right) = Pair(1, &quot;one&quot;)
    val list = listOf(1, 2, 3)
    for ((index, value) in list.withIndex()) {}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Pair과 같이 값과 쌍으로 이루어진 객체의 값을 분해해서 받을 수 있는데, 이것을 구조 분해 선언이라고 한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;&lt;b&gt;문자열 나누기&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;p&gt;자바의 split과 비슷하게 사용하면 되지만, 조금더 확장된 함수를 제공한다. 아래와 같이 문자열 하나 혹은 문자열 여러개를 넘겨 여러 구분 문자열로 나눌수도 있고, 혹은 직접 정규식을 생성해서 split의 구분문자 정규식을 넘길 수도 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1600295004118&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main() {
    val str = &quot;12.345-6.A&quot;

    println(str.split(&quot;.&quot;, &quot;-&quot;))
    println(str.split(&quot;\\.|-&quot;.toRegex()))
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;&lt;b&gt;Stirng의 substring 함수&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;pre id=&quot;code_1600295530917&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main() {
    val path = &quot;/Users/levi/kotlin/chapter3.kt&quot;
    val directory = path.substringBeforeLast(&quot;/&quot;)
    val fullName = path.substringAfterLast(&quot;/&quot;)
    val fileName = fullName.substringBefore(&quot;.&quot;)
    val extension = fullName.substringAfter(&quot;.&quot;)
    println(&quot;Dir : $directory, FullName : $fullName, FileName : $fileName, Extension : $extension&quot;)
}

-&amp;gt;
Dir : /Users/levi/kotlin, FullName : chapter3.kt, FileName : chapter3, Extension : kt&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;&lt;b&gt;로컬 함수와 확장&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;p&gt;코틀린에서는 중복된 로직을 깔끔하게 리팩토링할 수 있는 해법이 있다. 코틀린에서는 함수에서 추출한 함수를 원 ㅎ마수 내부에 중첩시킬 수 있고, 그렇게 하면 문법적인 부가 비용을 들이지 않고 깔끔하게 코드를 조작할 수 있다. 바로 예시를 살펴보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1600301501196&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class User(val id: Long, val name: String, val address: String) {
    fun saveUser(user: User) {
        if (user.name.isEmpty()) {
            throw IllegalArgumentException(&quot;empty user name&quot;)
        }
        
        if (user.address.isEmpty()) {
            throw IllegalArgumentException(&quot;empty user address&quot;)
        }
        
        // user를 데이터베이스에 저장하는 로직.
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위와 같은 코드가 있다. 해당 코드에서는 검즈하는 필드 값만 다를뿐 사실 같은 중복된 검증 로직(문자열이 비어있는지)을 가지고 있다. 이러한 중복된 코드 내용은 로컬 함수로 분리하면서 제거할 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1600301668381&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class User(val id: Long, val name: String, val address: String) {
    fun saveUser(user: User) {
        fun validate(user: User, value: String, fieldName: String) {
            if (value.isEmpty()) {
                throw IllegalArgumentException(&quot;empty ${user.id} $fieldName&quot;)
            }
        }
        
        validate(user, user.name, &quot;name&quot;)
        validate(user, user.address, &quot;address&quot;)

        // user를 데이터베이스에 저장하는 로직.
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위 예제는 중복된 검증 로직을 로컬 함수로 분리함으로써 중복을 제거하였다. 하지만, 뭔가 User 객체를 넘기는 것이 보기 싫다. 조금 더 개선하자면, 로컬 함수는 자신을 감싸고 있는 함수의 변수를 캡쳐링해서 가져갈 수있는 클로저로 동작하기 때문에 굳이 User 객체를 인자로 넘길 필요가 없이 아래처럼 리팩토링이 가능하다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1600301787349&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class User(val id: Long, val name: String, val address: String) {
    fun saveUser(user: User) {
        fun validate(value: String, fieldName: String) {
            if (value.isEmpty()) {
                throw IllegalArgumentException(&quot;empty ${user.id} $fieldName&quot;)
            }
        }

        validate(user.name, &quot;name&quot;)
        validate(user.address, &quot;address&quot;)

        // user를 데이터베이스에 저장하는 로직.
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;여기서 멈추지 않고, 조금더 깔끔하게 확장함수를 활용해 리팩토링해보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1600302141778&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class User(val id: Long, val name: String, val address: String) {
    private fun User.validate() {
        fun validateParam(value: String, fieldName: String) {
            fun validate(value: String, fieldName: String) {
                if (value.isEmpty()) {
                    throw IllegalArgumentException(&quot;empty ${this.id} $fieldName&quot;)
                }
            }            
        }
        validateParam(this.name, &quot;name&quot;)
        validateParam(this.address, &quot;address&quot;)
    }
    fun saveUser(user: User) {
        user.validate()
        // user를 데이터베이스에 저장하는 로직.
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;클래스 내부에 확장함수를 정의해서 saveUser 함수 내부는 훨씬더 군더더기 없는 코드로 리팩토링 되었다. 클래스 내부에 적용한 확장함수는 해당 클래스 내부에서만 사용가능하다는 것을 유의하자(private 키워드를 붙이지 않더라도 해당 클래스 내부에서만 사용가능하다.)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;여기까지 코틀린의 함수에 대한 내용은 간단히 다루어보았다. 사실 위 내용들보다 많은 내용이 있지만, 다음 포스팅들에서 다루어보지 못한 내용들을 더 많이 다루어볼 것이다.&lt;/p&gt;</description>
      <category>프로그래밍언어/Kotlin</category>
      <category>Function</category>
      <category>infix</category>
      <category>java</category>
      <category>jvm</category>
      <category>kotlin</category>
      <category>Spring</category>
      <category>로컬함수</category>
      <category>스프링</category>
      <category>자바</category>
      <category>코틀린</category>
      <author>여성게</author>
      <guid isPermaLink="true">https://coding-start.tistory.com/395</guid>
      <comments>https://coding-start.tistory.com/395#entry395comment</comments>
      <pubDate>Wed, 16 Sep 2020 13:05:33 +0900</pubDate>
    </item>
    <item>
      <title>Kotlin - 코틀린 간단한 문법 예제(함수, 변수, 클래스, for문, 예외처리, map iterator, 스마트 캐스팅..)</title>
      <link>https://coding-start.tistory.com/394</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bC38cf/btqIPSd8Oa5/lpQ8AKloCzXQzhM0y9kpn0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bC38cf/btqIPSd8Oa5/lpQ8AKloCzXQzhM0y9kpn0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bC38cf/btqIPSd8Oa5/lpQ8AKloCzXQzhM0y9kpn0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbC38cf%2FbtqIPSd8Oa5%2FlpQ8AKloCzXQzhM0y9kpn0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;오늘은 코틀린에 대해 아주 기초를 다루어본다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;&lt;b&gt;함수(Function)&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;p&gt;코틀린에서 함수는 &quot;fun&quot;이라는 키워드로 정의한다. 간단하게 리턴값이 있고, 없는 함수와 바디 내용이 식으로만 이루어졌을때 함수를 간략화 하는 방법은 아래와 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1600168546726&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * 리턴이 없는 함수
 */
fun helloWorld() {
    println(&quot;hello, world&quot;)
}

/**
 * 리턴값이 있는 함수
 */
fun max(a: Int, b: Int): Int {
    //코틀린의 if문은 식(리턴 값이 존재)이지 문(block, return이 없음)이 아니다.
    return if (a &amp;gt; b) a else b
}

/**
 * max 함수와 간략 버전
 * 함수의 본문이 식으로만 이루어져있다면, 아래처럼 간략하게 바꿀 수 있다.
 * 또한 반환 타입도 생략이 가능하다. 그 이유는 식이 반환하는 타입을 컴파일러가 추론하기 때문이다.
 * fun max2(a: Int, b: Int): Int = if (a &amp;gt; b) a else b
 * -&amp;gt;
 * fun max2(a: Int, b: Int) = if (a &amp;gt; b) a else b
 */
fun max2(a: Int, b: Int) = if (a &amp;gt; b) a else b&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;&lt;b&gt;변수(Variable)&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;p&gt;크게 어색하지 않은 키워드이다. 하지만 val은 자바의 final과 거의 비슷한 느낌인데, 선언시 반드시 초기화 식이 들어가지 않아도 된다. 그 이유는 블록내에 컴파일러에게 오직 한번만 할당한다라는 보장을 줄 수 있다면, 블록내에 여러군대에서 할당하는 코드가 들어가도 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1600168722648&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//타입을 명시하지 않아도 컴파일러가 타입을 추론한다.
val question = &quot;sample question&quot; -&amp;gt; String
val num = 42 -&amp;gt; Int
val yearsToCompute = 7.5e6 -&amp;gt; Double

//변수의 타입을 직접 명시해줄 수도 있다.
val answer: Int = 42

//변경, 재할당이 가능한 변수
//한번 타입이 추론되면 다른 곳에서 어싸인할때 다른 타입으로 못넣음.
var variable = 1

/**
 * val 키워드지만, 컴파일러가 오직 하나의 초기화 문장만
 * 실행됨이 확실하면 여러 곳에서 할당하는 코드가 들어갈 수 있다.
 */
fun variableExam(b: Boolean): String {
    val message: String

    if (b) {
        message = &quot;true&quot;
    } else {
        message = &quot;false&quot;
    }

    return message
}

/**
* 타입이 생략가능하다지만, 다른 타입을 재할당 할 수는 없다.
*/
var intValue = 1
intValue = &quot;123&quot; -&amp;gt; 컴파일에러&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;마지막에 var에 다른 타입으로 재할당이 불가능하지만,&amp;nbsp; 변환 함수나 강제 형변환을 통해 다른 타입의 값을 변수에 재할당 가능하다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;&lt;b&gt;문자열 템플릿&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;문자열을 다룰때 &quot;$&quot; 키워드로 변수값을 그대로 넣을 수 있다. 마치 자바에서 &quot;Hello&quot; + name + &quot;!&quot; 과 비슷하지만 조금더 간결하게 작성가능하다. 또한 &quot;${}&quot;를 이용하여 변수명만 아니라, 복잡한 식도 넣을 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1600169293316&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//컴파일된 코드는 StringBuilder를 사용하고, 문자열 상수와 변수의 값을 append로 문자열 빌더 뒤에 추가한다.
fun main(args: Array&amp;lt;String&amp;gt;) {
    val name = if (args.isNotEmpty()) args[0] else &quot;Kotlin&quot;
    println(&quot;Hello, $name!&quot;)
}

fun stringTemplate(args: Array&amp;lt;String&amp;gt;) {
    if (args.isNotEmpty()) {
        println(&quot;Hello, ${args[0]}!&quot;)
    }
}

fun stringTemplate2(args: Array&amp;lt;String&amp;gt;) {
    if (args.isNotEmpty()) {
        println(&quot;Hello, ${if (args.isNotEmpty()) args[0] else &quot;Kotlin&quot;}!&quot;)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;&lt;b&gt;클래스와 프로퍼티&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;p&gt;자바의 클래스와 코틀린의 클래스 선언 방법을 비교해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1600170839968&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * Java class definition
 */
public class Person {
    private final String name;
    
    public Person(String name) {
        this.name = name;
    }
    
    public String getName() {
        return this.name;
    }
}

/**
 * Kotlin class definition
 */
class Person(val name: String)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;자바에서는 프로퍼티가 증가할 수록 생성자에 매개변수가 증가함에 따라 내부 this 연산자로 프로퍼티를 초기화하는 코드도 증가한다.(물론 롬복이라는 라이브러리를 사용하면 되지만..) 코틀린은 프로퍼티가 많아져도 아주 간단하게 클래스를 정의할 수 있다. 또한 코틀린은 기본 접근제어자가 public 이기 때문에 public 키워드도 생략가능하다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1600171309622&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * Kotlin class definition
 */
class Person(val name: String,
             var age: Int)

fun main(args: Array&amp;lt;String&amp;gt;) {
    val person = Person(&quot;levi&quot;, 29)
    println(person.name)
    println(person.age)
    person.age = 30
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;생성자에서 var로 프로퍼티를 선언하면, 실제 변경가능한 프로퍼티로 생성된다. 자바와 달리 setxx, getxx이 필요없고, 실제 프로퍼티를 참조해 값을 가져오거나 값을 변경할 수 있다.(프로퍼티를 참조하면 내부적으로 getter, setter를 호출해준다.)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1600171623655&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * Kotlin class definition
 */
class Person(val name: String,
             var age: Int) {
    val isProgrammer: Boolean
        get() {
            return name == &quot;levi&quot;
        }
        //get() = name == &quot;levi&quot;
}

fun main(args: Array&amp;lt;String&amp;gt;) {
    val person = Person(&quot;levi&quot;, 29)
    println(person.isProgrammer)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;또한 위 예제처럼 실제 초기화 시점에는 값을 받을 필요가 없는 프로퍼티의 경우 직접 프로퍼티의 접근자(getter)를 정의해줄 수 있다. 함수로 &lt;i&gt;&lt;u&gt;&lt;b&gt;fun isProgrammer() = name == &quot;levi&quot;&lt;/b&gt;&lt;/u&gt;&lt;/i&gt;를 정의할 수 있지만, 성능상 차이는 없고, 가독성 차이 뿐이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;&lt;b&gt;디렉토리와 패키지&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;p&gt;코틀린은 자바와 패키지에 대한 개념이 비슷하다. 하지만 아래와 같이, 최상위 함수가 정의되어있다면 다른 패키지에서 해당 함수만 임포트해서 사용가능하다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1600172640486&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.example.kotlin.ch_1

class Rectangle(val height: Int, val width: Int) {
    val isSquare: Boolean
        get() = height == width

    fun sumHeightAndWidth() = height + width
}

fun createRandomRectangle(): Rectangle {
    val random = Random()
    return Rectangle(random.nextInt(), random.nextInt())
}

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

import com.example.kotlin.ch_1.createRandomRectangle

fun main(args: Array&amp;lt;String&amp;gt;) {
    var rectangle = createRandomRectangle()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;또한 같은 패키지라면, 함수를 임포트할 필요없이 얼마든지 가져다 사용할 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;&lt;b&gt;선택 표현과 처리: enum과 when&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;p&gt;enum 클래스는 아래와 같이 선언한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1600172990842&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;enum class Color(val r: Int, val g: Int, val b: Int) {
    RED(255, 0, 0), ORANGE(255, 165, 0),
    YELLOW(255, 255, 0), GREEN(0, 255, 0);
    
    fun rgb() = (r * 256 + g) * 256 + b
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;when 절은 자바의 switch case와 상응하는 문법이다. if else와 같이 리턴값을 가지는 식이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1600173288743&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun getMnemonic(color: Color) = when (color) {
    Color.RED -&amp;gt; &quot;GOOD&quot;
    Color.ORANGE -&amp;gt; &quot;NOT BAD&quot;
    Color.GREEN, Color.YELLOW -&amp;gt; &quot;BAD&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;자바의 switch 문과 달리 코틀린의 when은 임의의 객체를 인자로 받을 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1600175917987&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#setOf Set 객체를 만드는 함수
fun mix(c1: Color, c2: Color) = when (setOf(c1, c2)) {
    setOf(Color.RED, Color.ORANGE) -&amp;gt; &quot;first&quot;
    setOf(Color.YELLOW, Color.GREEN) -&amp;gt; &quot;second&quot;
    else -&amp;gt; throw Exception(&quot;exception&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;또한 when은 인자가 없이 만들 수도 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1600176109416&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun mixOptimized(c1: Color, c2: Color) = when {
    (c1 == Color.RED &amp;amp;&amp;amp; c2 == Color.YELLOW) -&amp;gt; &quot;first&quot;
    (c1 == Color.YELLOW &amp;amp;&amp;amp; c2 == Color.GREEN) -&amp;gt; &quot;second&quot;
    else -&amp;gt; throw Exception(&quot;exception&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위처럼 인자가 없는 when 식을 사용하면, 각 분기의 조건이 Boolean을 반환하는 식을 넣으면 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;&lt;b&gt;스마트 캐스트: 타입 검사와 타입 캐스트를 조합&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;p&gt;자바와 달리 코틀린은 명시적으로 타입 캐스팅하는 코드를 넣을 필요가 없다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1600176820015&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface Expr

class Num(val value: Int) : Expr

class Sum(val left: Expr, val right: Expr) : Expr

fun eval(e: Expr): Int =
        if (e is Num) {
            val num = e as Num
            num.value
        } else if (e is Sum) {
            eval(e.left) + eval(e.right)
        } else {
            throw IllegalArgumentException(&quot;Unknown expression&quot;)
        }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;첫번째 if문은 자바처럼 명시적으로 &quot;as&quot; 키워드를 사용해 타입 캐스팅을 하였다. 하지만 else if 문을 보면 딱히 타입 캐스팅을 하지 않았는데도, Sum 타입으로 변환이 되었다. 이것을 스마트 캐스팅이라고 한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이번엔 위 eval 함수를 when식로 리팩토링 해보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1600176915145&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun eval(e: Expr): Int =
        when (e) {
            is Num -&amp;gt; {
                val num = e as Num
                num.value
            }
            is Sum -&amp;gt; {
                eval(e.left) + eval(e.right)
            }
            else -&amp;gt; {
                throw IllegalArgumentException(&quot;Unknown expression&quot;)
            }
        }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;코틀린에서 조금 특이하게 동등성 검사가 아닌 다른 기능에도 when식을 사용할 수 있다. 그리고 조금 특이한것이 블록 내에 return이 생략되어 있는 것을 볼 수 있다. 이 말은 &quot;블록의 마지막 식이 블록의 결과&quot;라는 규칙이 항상 성립되는 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;&lt;b&gt;for문&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;p&gt;코틀린은 자바의 forEach문만 지원한다. 이말은 for(int i=0; i&amp;lt;10; i++) 같은 문법을 지원하지 않는것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1600178503073&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.example.kotlin.ch_2

fun forEach() {
    val list = listOf(&quot;a&quot;, &quot;b&quot;, &quot;c&quot;)

    for (str in list) {
        println(str)
    }
}

fun forEach1() {
	val list = arrayListOf(10, 11, 12)
    for ((index, elem) in list.withIndex()) {
    	println(&quot;$index, $elem&quot;)
    }
}

fun forEach2() {
    //range 1부터 10까지 포함
    val oneToTen = 1..10

    for (i in oneToTen) {
        println(i)
    }
}

fun forEach3() {
    for (i in 10 downTo 1 step 2) {
        println(i)
    }
}

fun main() {
    forEach()
    println(&quot;===============&quot;)
    forEach2()
    println(&quot;===============&quot;)
    forEach3()
}

-&amp;gt;
a
b
c
===============
0, 10
1, 11
2, 12
===============
1
2
3
4
5
6
7
8
9
10
===============
10
8
6
4
2&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;특이하게 코틀린은 range라는 변수를 선언할 수 있어서 자바의 for문처럼 흉내를 낼 수 있다. 또한 downTo, step 문법을 지원해 역순으로 출력하는 것도 가능하고, withIndex로 리스트의 인덱스와 요소를 같이 받을 수도 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;&lt;b&gt;맵에 대한 for문&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;pre id=&quot;code_1600179146392&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun forEach4() {
    val lowerUpperCaseMap = HashMap&amp;lt;Char, Char&amp;gt;()

    for (c in 'A'..'F') {
        val lowerCase = c.toLowerCase()
        //자바의 map.put(lowerCase, c)와 같다.
        lowerUpperCaseMap[lowerCase] = c
    }

    for ((lower, upper) in lowerUpperCaseMap) {
        println(&quot;$lower $upper&quot;)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;&lt;b&gt;in으로 컬렉션이나 범위의 원소 검사&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;pre id=&quot;code_1600179394077&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun recognize(c: Char) = when (c) {
    //'0' &amp;lt;= c &amp;amp;&amp;amp; c &amp;lt;= '9'
    in '0'..'9' -&amp;gt; &quot;It's digit!&quot;
    //'a' &amp;lt;= c &amp;amp;&amp;amp; c &amp;lt;= 'z'
    in 'a'..'z', in 'A'..'Z' -&amp;gt; &quot;It's a letter&quot;
    else -&amp;gt; &quot;I don't know&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Comparable 등이 구현되어 있다면, 일반 오브젝트에서도 &quot;in&quot; 사용이 가능하다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;&lt;b&gt;코틀린의 예외 처리&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;p&gt;코틀린은 자바와 같이 checked exception과 unchecked exception을 구분하지 않는다. 즉, 예외 처리를 강제하지 않아며, 메서드에 throws 문을 명시하지 않아도 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1600180060831&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun readNumber(reader: BufferedReader): Int? {
    try {
        val line = reader.readLine() //checked exception이 발생
        return Integer.parseInt(line)
    } catch (e: NumberFormatException) {
        return null
    } finally {
        reader.close()
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위 코드의 경우 reader.readLine()이 IOException(checked exception)을 발생시키지만, 코틀린에서는 어디에도 다시 throw하거나, 캐치해서 처리하지 않는다. 즉, checked exception을 강제하지 않는 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;또한 try-catch는 물론 식이므로 아래와 같이 코드를 변경할 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1600180387992&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun readNumber2(reader: BufferedReader) {
    val number = try {
        Integer.parseInt(reader.readLine())
    } catch (e: NumberFormatException) {
        null
    }
    
    println(number)
}

fun readNumber3(reader: BufferedReader) {
    val number = try {
        Integer.parseInt(reader.readLine())
    } catch (e: NumberFormatException) {
        return
    }

    println(number)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;하지만 조금 다른 것이 catch에서 null을 반환하냐, return 키워드를 쓰냐의 차이인데 return 키워드를 사용하면 catch문 아래의 로직은 수행하지 않고, 해당 메서드가 스택에서 반환된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;여기까지 아주 간단한 코틀린 문법에 대해 다루어 보았다. 다음 포스팅에는 조금더 자세히 코틀린에 대해 다루어본다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;lt;참고 서적&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;232&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bl6LaX/btqIM7py8SV/G6ESLChqTcynpSIxy7Ho4K/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bl6LaX/btqIM7py8SV/G6ESLChqTcynpSIxy7Ho4K/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bl6LaX/btqIM7py8SV/G6ESLChqTcynpSIxy7Ho4K/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbl6LaX%2FbtqIM7py8SV%2FG6ESLChqTcynpSIxy7Ho4K%2Fimg.jpg&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;232&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>프로그래밍언어/Kotlin</category>
      <category>java</category>
      <category>jvm</category>
      <category>kotlin</category>
      <category>Spring</category>
      <category>스프링</category>
      <category>자바</category>
      <category>코틀린</category>
      <author>여성게</author>
      <guid isPermaLink="true">https://coding-start.tistory.com/394</guid>
      <comments>https://coding-start.tistory.com/394#entry394comment</comments>
      <pubDate>Tue, 15 Sep 2020 20:33:12 +0900</pubDate>
    </item>
    <item>
      <title>Kubernetes - kustomize를 이용한 쿠버네티스 오브젝트 관리</title>
      <link>https://coding-start.tistory.com/388</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MRgVl/btqHsbndKV3/ndr4feGBICKXZKMClqt4uK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MRgVl/btqHsbndKV3/ndr4feGBICKXZKMClqt4uK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MRgVl/btqHsbndKV3/ndr4feGBICKXZKMClqt4uK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMRgVl%2FbtqHsbndKV3%2Fndr4feGBICKXZKMClqt4uK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;오늘 다루어볼 내용은 kustomize이다. Kustomize는 kustomization 파일을 이용해 kubernetes 오브젝트를 사용자가 원하는 대로 변경(customize)하는 도구이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;모든 예제는 아래 깃헙 kube-kustomize 디렉토리에 있다.&lt;/p&gt;
&lt;figure id=&quot;og_1598774790517&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;object&quot; data-og-title=&quot;yoonyeoseong/kubernetes-sample&quot; data-og-description=&quot;Kubernetes(쿠버네티스) sample. Contribute to yoonyeoseong/kubernetes-sample development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/yoonyeoseong/kubernetes-sample/tree/master/kube-kustomize&quot; data-og-url=&quot;https://github.com/yoonyeoseong/kubernetes-sample&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bjd3XI/hyHjTrNAeU/ZEJQyw6h80KsdO2n7mpLuk/img.jpg?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400,https://scrap.kakaocdn.net/dn/pZWXa/hyHjUYxX2K/23xZZtgg2Nz49hSoKFeZcK/img.png?width=1211&amp;amp;height=630&amp;amp;face=0_0_1211_630,https://scrap.kakaocdn.net/dn/bTH1Mu/hyHlbEnWzd/8Osotjjb4nssCkNZ70r5TK/img.png?width=696&amp;amp;height=668&amp;amp;face=0_0_696_668&quot;&gt;&lt;a href=&quot;https://github.com/yoonyeoseong/kubernetes-sample/tree/master/kube-kustomize&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/yoonyeoseong/kubernetes-sample/tree/master/kube-kustomize&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bjd3XI/hyHjTrNAeU/ZEJQyw6h80KsdO2n7mpLuk/img.jpg?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400,https://scrap.kakaocdn.net/dn/pZWXa/hyHjUYxX2K/23xZZtgg2Nz49hSoKFeZcK/img.png?width=1211&amp;amp;height=630&amp;amp;face=0_0_1211_630,https://scrap.kakaocdn.net/dn/bTH1Mu/hyHlbEnWzd/8Osotjjb4nssCkNZ70r5TK/img.png?width=696&amp;amp;height=668&amp;amp;face=0_0_696_668');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;yoonyeoseong/kubernetes-sample&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;Kubernetes(쿠버네티스) sample. Contribute to yoonyeoseong/kubernetes-sample development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;kustomization 파일을 포함하는 디렉터리 내의 리소스를 보거나 실제 클러스터에 리소스를 적용하려면 다음 명령어를 이용한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1598773854512&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#kustomize가 적용된 설정파일 결과를 보여준다. 
&amp;gt; kubectl kustomize &amp;lt;kustomization_directory&amp;gt; 
#실제 kustomize 리소스를 클러스터에 적용한다. 
&amp;gt; kubectl apply -k &amp;lt;kustomization_directory&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;a id=&quot;user-content-kustomize&quot; href=&quot;https://github.com/yoonyeoseong/kubernetes-sample/tree/master/kube-kustomize#kustomize&quot; aria-hidden=&quot;true&quot;&gt;&lt;/a&gt;&lt;i&gt;&lt;b&gt;Kustomize&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;p&gt;Kustomize는 쿠버네티스 구성을 사용자 정의화하는 도구이다. 이는 애플리케이션 구성 파일을 관리하기 위해 다음 기능들을 가진다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;다른 소스에서 리소스 생성&lt;/li&gt;
&lt;li&gt;리소스에 대한 교차 편집 필드 설정&lt;/li&gt;
&lt;li&gt;리소스 집합을 구성하고 사용자 정의&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;&lt;a id=&quot;user-content-교차-편집-필드-설정&quot; href=&quot;https://github.com/yoonyeoseong/kubernetes-sample/tree/master/kube-kustomize#%EA%B5%90%EC%B0%A8-%ED%8E%B8%EC%A7%91-%ED%95%84%EB%93%9C-%EC%84%A4%EC%A0%95&quot; aria-hidden=&quot;true&quot;&gt;&lt;/a&gt;&lt;i&gt;&lt;b&gt;교차 편집 필드 설정&lt;/b&gt;&lt;/i&gt;&lt;/h2&gt;
&lt;p&gt;프로젝트 내 모든 쿠버네티스 리소스에 교차 편집 필드를 설정하는 것은 꽤나 일반적이다. 교차 편집 필드를 설정하는 몇 가지 사용 사례는 다음과 같다.&lt;/p&gt;
&lt;ul&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;/ul&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1598773895187&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;object&quot; data-og-title=&quot;yoonyeoseong/kubernetes-sample&quot; data-og-description=&quot;Kubernetes(쿠버네티스) sample. Contribute to yoonyeoseong/kubernetes-sample development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/yoonyeoseong/kubernetes-sample/tree/master/kube-kustomize/kustomize-upsert-field&quot; data-og-url=&quot;https://github.com/yoonyeoseong/kubernetes-sample&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/djtJhb/hyHjJiqHFX/dEwgi3SS3hIuXikUZXIKw1/img.jpg?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400,https://scrap.kakaocdn.net/dn/ffQkP/hyHjViSSKp/Yrg8pi7SJGBR7FDqhUkwK1/img.png?width=1211&amp;amp;height=630&amp;amp;face=0_0_1211_630&quot;&gt;&lt;a href=&quot;https://github.com/yoonyeoseong/kubernetes-sample/tree/master/kube-kustomize/kustomize-upsert-field&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/yoonyeoseong/kubernetes-sample/tree/master/kube-kustomize/kustomize-upsert-field&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/djtJhb/hyHjJiqHFX/dEwgi3SS3hIuXikUZXIKw1/img.jpg?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400,https://scrap.kakaocdn.net/dn/ffQkP/hyHjViSSKp/Yrg8pi7SJGBR7FDqhUkwK1/img.png?width=1211&amp;amp;height=630&amp;amp;face=0_0_1211_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;yoonyeoseong/kubernetes-sample&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;Kubernetes(쿠버네티스) sample. Contribute to yoonyeoseong/kubernetes-sample development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;pre id=&quot;code_1598773987544&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# deployment.yaml을 생성
cat &amp;lt;&amp;lt;EOF &amp;gt;./deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx
EOF

cat &amp;lt;&amp;lt;EOF &amp;gt;./kustomization.yaml
namespace: my-namespace
namePrefix: dev-
nameSuffix: &quot;-001&quot;
commonLabels:
  app: bingo
commonAnnotations:
  oncallPager: 800-555-1212
resources:
- deployment.yaml
EOF&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1598774013222&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;gt; kubectl kustomize ./kube-kustomize/kustomize-upsert-field

apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    oncallPager: 800-555-1212
  labels:
    app: bingo
  name: dev-nginx-deployment-001
  namespace: my-namespace
spec:
  selector:
    matchLabels:
      app: bingo
  template:
    metadata:
      annotations:
        oncallPager: 800-555-1212
      labels:
        app: bingo
    spec:
      containers:
      - image: nginx
        name: nginx&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;&lt;a id=&quot;user-content-구성composition&quot; href=&quot;https://github.com/yoonyeoseong/kubernetes-sample/tree/master/kube-kustomize#%EA%B5%AC%EC%84%B1composition&quot; aria-hidden=&quot;true&quot;&gt;&lt;/a&gt;&lt;i&gt;&lt;b&gt;구성(composition)&lt;/b&gt;&lt;/i&gt;&lt;/h2&gt;
&lt;p&gt;한 파일에 deployment, service 등을 정의하는 것은 일반적이다. kustomize는 서로 다른 리소스들을 하나의 파일로 구성할 수 있게 지원한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1598774082820&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;object&quot; data-og-title=&quot;yoonyeoseong/kubernetes-sample&quot; data-og-description=&quot;Kubernetes(쿠버네티스) sample. Contribute to yoonyeoseong/kubernetes-sample development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/yoonyeoseong/kubernetes-sample/tree/master/kube-kustomize/kustomize-composition&quot; data-og-url=&quot;https://github.com/yoonyeoseong/kubernetes-sample&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/blNT6l/hyHjL1CeAf/7STqe0vXsshhYpGpdeu951/img.jpg?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400,https://scrap.kakaocdn.net/dn/Gryjo/hyHh4S1q1u/GpJjYKA6yS1zfcymkkYU8k/img.png?width=1211&amp;amp;height=630&amp;amp;face=0_0_1211_630,https://scrap.kakaocdn.net/dn/DsBz9/hyHlhR8Jib/8pNJ2kisku7BoQzkpzDXU0/img.png?width=696&amp;amp;height=668&amp;amp;face=0_0_696_668&quot;&gt;&lt;a href=&quot;https://github.com/yoonyeoseong/kubernetes-sample/tree/master/kube-kustomize/kustomize-composition&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/yoonyeoseong/kubernetes-sample/tree/master/kube-kustomize/kustomize-composition&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/blNT6l/hyHjL1CeAf/7STqe0vXsshhYpGpdeu951/img.jpg?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400,https://scrap.kakaocdn.net/dn/Gryjo/hyHh4S1q1u/GpJjYKA6yS1zfcymkkYU8k/img.png?width=1211&amp;amp;height=630&amp;amp;face=0_0_1211_630,https://scrap.kakaocdn.net/dn/DsBz9/hyHlhR8Jib/8pNJ2kisku7BoQzkpzDXU0/img.png?width=696&amp;amp;height=668&amp;amp;face=0_0_696_668');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;yoonyeoseong/kubernetes-sample&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;Kubernetes(쿠버네티스) sample. Contribute to yoonyeoseong/kubernetes-sample development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1598774047783&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# deployment.yaml 파일 생성
cat &amp;lt;&amp;lt;EOF &amp;gt; deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-nginx
spec:
  selector:
    matchLabels:
      run: my-nginx
  replicas: 2
  template:
    metadata:
      labels:
        run: my-nginx
    spec:
      containers:
      - name: my-nginx
        image: nginx
        ports:
        - containerPort: 80
EOF

# service.yaml 파일 생성
cat &amp;lt;&amp;lt;EOF &amp;gt; service.yaml
apiVersion: v1
kind: Service
metadata:
  name: my-nginx
  labels:
    run: my-nginx
spec:
  ports:
  - port: 80
    protocol: TCP
  selector:
    run: my-nginx
EOF

# 이들을 구성하는 kustomization.yaml 생성
cat &amp;lt;&amp;lt;EOF &amp;gt;./kustomization.yaml
resources:
- deployment.yaml
- service.yaml
EOF&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1598774093355&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;gt; kubectl kustomize /kube-kustomize/kustomize-composition

apiVersion: v1
kind: Service
metadata:
  labels:
    run: my-nginx
  name: my-nginx
spec:
  ports:
    - port: 80
      protocol: TCP
  selector:
    run: my-nginx
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-nginx
spec:
  replicas: 2
  selector:
    matchLabels:
      run: my-nginx
  template:
    metadata:
      labels:
        run: my-nginx
    spec:
      containers:
        - image: nginx
          name: my-nginx
          ports:
            - containerPort: 80&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;&lt;a id=&quot;user-content-사용자-정의user-patch-define&quot; href=&quot;https://github.com/yoonyeoseong/kubernetes-sample/tree/master/kube-kustomize#%EC%82%AC%EC%9A%A9%EC%9E%90-%EC%A0%95%EC%9D%98user-patch-define&quot; aria-hidden=&quot;true&quot;&gt;&lt;/a&gt;&lt;i&gt;&lt;b&gt;사용자 정의(user patch define)&lt;/b&gt;&lt;/i&gt;&lt;/h2&gt;
&lt;p&gt;패치는 리소스에 다른 사용자 정의를 적용하는 데 사용할 수 있다. Kustomize는 patchesStrategicMerge와 patchesJson6902를 통해 서로 다른 패치 메커니즘을 지원한다. patchesStrategicMerge는 파일 경로들의 리스트이다. 각각의 파일은 patchesStrategicMerge로 분석될 수 있어야 한다. 패치 내부의 네임은 반드시 이미 읽혀진 리소스 네임(ex. deployment.yaml 안의 이름)과 일치해야 한다. 한 가지 일을 하는 작은 패치가 권장된다. 예를 들기 위해 디플로이먼트 레플리카 숫자를 증가시키는 하나의 패치와 메모리 상한을 설정하는 다른 패치를 생성한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1598774133454&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;object&quot; data-og-title=&quot;yoonyeoseong/kubernetes-sample&quot; data-og-description=&quot;Kubernetes(쿠버네티스) sample. Contribute to yoonyeoseong/kubernetes-sample development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/yoonyeoseong/kubernetes-sample/tree/master/kube-kustomize/kustomize-patchesStrategicMerge&quot; data-og-url=&quot;https://github.com/yoonyeoseong/kubernetes-sample&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cstOiY/hyHjSGrt4k/43ygZ0KPLEGpGfvG5KL3sk/img.jpg?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400,https://scrap.kakaocdn.net/dn/vFtes/hyHjXOu1Dd/mdv9n6xmMKcTOBPS56p4mk/img.png?width=1211&amp;amp;height=630&amp;amp;face=0_0_1211_630&quot;&gt;&lt;a href=&quot;https://github.com/yoonyeoseong/kubernetes-sample/tree/master/kube-kustomize/kustomize-patchesStrategicMerge&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/yoonyeoseong/kubernetes-sample/tree/master/kube-kustomize/kustomize-patchesStrategicMerge&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cstOiY/hyHjSGrt4k/43ygZ0KPLEGpGfvG5KL3sk/img.jpg?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400,https://scrap.kakaocdn.net/dn/vFtes/hyHjXOu1Dd/mdv9n6xmMKcTOBPS56p4mk/img.png?width=1211&amp;amp;height=630&amp;amp;face=0_0_1211_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;yoonyeoseong/kubernetes-sample&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;Kubernetes(쿠버네티스) sample. Contribute to yoonyeoseong/kubernetes-sample development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1598774143394&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;object&quot; data-og-title=&quot;yoonyeoseong/kubernetes-sample&quot; data-og-description=&quot;Kubernetes(쿠버네티스) sample. Contribute to yoonyeoseong/kubernetes-sample development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/yoonyeoseong/kubernetes-sample/tree/master/kube-kustomize/kustomize-patchesJson6902&quot; data-og-url=&quot;https://github.com/yoonyeoseong/kubernetes-sample&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/vmwBv/hyHjMe9J6H/LGTYCf0pLhIHTWXApBZX40/img.jpg?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400,https://scrap.kakaocdn.net/dn/cOEwn4/hyHjMlVdSW/HDfUQWbGNh5A4AeKOCMRK0/img.png?width=1211&amp;amp;height=630&amp;amp;face=0_0_1211_630,https://scrap.kakaocdn.net/dn/kHiqt/hyHk79PfKU/sZsPzp5uq4Cb9b2pV1qZ1K/img.png?width=696&amp;amp;height=668&amp;amp;face=0_0_696_668&quot;&gt;&lt;a href=&quot;https://github.com/yoonyeoseong/kubernetes-sample/tree/master/kube-kustomize/kustomize-patchesJson6902&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/yoonyeoseong/kubernetes-sample/tree/master/kube-kustomize/kustomize-patchesJson6902&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/vmwBv/hyHjMe9J6H/LGTYCf0pLhIHTWXApBZX40/img.jpg?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400,https://scrap.kakaocdn.net/dn/cOEwn4/hyHjMlVdSW/HDfUQWbGNh5A4AeKOCMRK0/img.png?width=1211&amp;amp;height=630&amp;amp;face=0_0_1211_630,https://scrap.kakaocdn.net/dn/kHiqt/hyHk79PfKU/sZsPzp5uq4Cb9b2pV1qZ1K/img.png?width=696&amp;amp;height=668&amp;amp;face=0_0_696_668');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;yoonyeoseong/kubernetes-sample&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;Kubernetes(쿠버네티스) sample. Contribute to yoonyeoseong/kubernetes-sample development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1598774179387&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# deployment.yaml 파일 생성
cat &amp;lt;&amp;lt;EOF &amp;gt; deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-nginx
spec:
  selector:
    matchLabels:
      run: my-nginx
  replicas: 2
  template:
    metadata:
      labels:
        run: my-nginx
    spec:
      containers:
      - name: my-nginx
        image: nginx
        ports:
        - containerPort: 80
EOF

# increase_replicas.yaml 패치 생성
cat &amp;lt;&amp;lt;EOF &amp;gt; increase_replicas.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-nginx
spec:
  replicas: 3
EOF

# 다른 패치로 set_memory.yaml 생성
cat &amp;lt;&amp;lt;EOF &amp;gt; set_memory.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-nginx
spec:
  template:
    spec:
      containers:
      - name: my-nginx
        resources:
        limits:
          memory: 512Mi
EOF

cat &amp;lt;&amp;lt;EOF &amp;gt;./kustomization.yaml
resources:
- deployment.yaml
patchesStrategicMerge:
- increase_replicas.yaml
- set_memory.yaml
EOF&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1598774197554&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;gt; kubectl kustomize /kube-kustomize/kustomize-patchesStrategicMerge

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      run: my-nginx
  template:
    metadata:
      labels:
        run: my-nginx
    spec:
      containers:
        - image: nginx
          name: my-nginx
          ports:
            - containerPort: 80
          resources:
            limits:
              memory: 512Mi&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;모든 리소스 또는 필드가 patchesStrategicMerge를 지원하는 것은 아니다. 임의의 리소스 내 임의의 필드의 수정을 지원하기 위해, Kustomize는 patchesJson6902를 통한 JSON 패치 적용을 제공한다. Json 패치의 정확한 리소스를 찾기 위해, 해당 리소스의 group, version, kind, name이 kustomization.yaml 내에 명시될 필요가 있다. 예를 들면, patchesJson6902를 통해 디플로이먼트의 리소스만 증가시킬 수 있다. 또한 patchesStrategicMerge, patchesJson6902를 같이 혼합해서 사용도 가능하다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1598774221528&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-nginx
spec:
  selector:
    matchLabels:
      run: my-nginx
  replicas: 2
  template:
    metadata:
      labels:
        run: my-nginx
    spec:
      containers:
        - name: my-nginx
          image: nginx
          ports:
            - containerPort: 80
          resources:
            limits:
              memory: 256Mi

#patch-replica.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-nginx
spec:
  replicas: 3

#patch-resource.yaml
- op: replace
  path: /spec/template/spec/containers/0/resources/limits/memory
  value: 512Mi

#kustomization.yaml
resources:
  - deployment.yaml

patchesStrategicMerge:
  - patch-replica.yaml

patchesJson6902:
  - target:
      kind: Deployment
      name: my-nginx
      group: apps
      version: v1
    path: patch-resource.yaml&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1598774244804&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;gt; kubectl kustomize /kube-kustomize/kustomize-patchesJson6902

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      run: my-nginx
  template:
    metadata:
      labels:
        run: my-nginx
    spec:
      containers:
        - image: nginx
          name: my-nginx
          ports:
            - containerPort: 80
          resources:
            limits:
              memory: 512Mi&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;patchesJson6902는 &quot;replace&quot;라는 오퍼레이션 말고, add, remove, move, copy, test라는 오퍼레이션도 존재한다.&lt;/p&gt;
&lt;h2&gt;&lt;a id=&quot;user-content-patch-images&quot; href=&quot;https://github.com/yoonyeoseong/kubernetes-sample/tree/master/kube-kustomize#patch-images&quot; aria-hidden=&quot;true&quot;&gt;&lt;/a&gt;&lt;i&gt;&lt;b&gt;patch images&lt;/b&gt;&lt;/i&gt;&lt;/h2&gt;
&lt;p&gt;patch 파일을 생성하지 않고, 컨테이너의 이미지를 재정의 할 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1598774292850&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;object&quot; data-og-title=&quot;yoonyeoseong/kubernetes-sample&quot; data-og-description=&quot;Kubernetes(쿠버네티스) sample. Contribute to yoonyeoseong/kubernetes-sample development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/yoonyeoseong/kubernetes-sample/tree/master/kube-kustomize/kustomize-patch-images&quot; data-og-url=&quot;https://github.com/yoonyeoseong/kubernetes-sample&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bGxiRI/hyHjXOu5w0/0Lw8KvbxClo0KPzk9X2NBk/img.jpg?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400,https://scrap.kakaocdn.net/dn/bb149U/hyHlhR8PHq/UrbqHXOJmlavcVxh47woLK/img.png?width=1211&amp;amp;height=630&amp;amp;face=0_0_1211_630,https://scrap.kakaocdn.net/dn/Vh0NU/hyHjKPaJE2/MYgKgZwtI6IOOfvE6jTaZ1/img.png?width=696&amp;amp;height=668&amp;amp;face=0_0_696_668&quot;&gt;&lt;a href=&quot;https://github.com/yoonyeoseong/kubernetes-sample/tree/master/kube-kustomize/kustomize-patch-images&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/yoonyeoseong/kubernetes-sample/tree/master/kube-kustomize/kustomize-patch-images&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bGxiRI/hyHjXOu5w0/0Lw8KvbxClo0KPzk9X2NBk/img.jpg?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400,https://scrap.kakaocdn.net/dn/bb149U/hyHlhR8PHq/UrbqHXOJmlavcVxh47woLK/img.png?width=1211&amp;amp;height=630&amp;amp;face=0_0_1211_630,https://scrap.kakaocdn.net/dn/Vh0NU/hyHjKPaJE2/MYgKgZwtI6IOOfvE6jTaZ1/img.png?width=696&amp;amp;height=668&amp;amp;face=0_0_696_668');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;yoonyeoseong/kubernetes-sample&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;Kubernetes(쿠버네티스) sample. Contribute to yoonyeoseong/kubernetes-sample development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1598774305093&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;cat &amp;lt;&amp;lt;EOF &amp;gt; deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-nginx
spec:
  selector:
    matchLabels:
      run: my-nginx
  replicas: 2
  template:
    metadata:
      labels:
        run: my-nginx
    spec:
      containers:
      - name: my-nginx
        image: nginx
        ports:
        - containerPort: 80
EOF

cat &amp;lt;&amp;lt;EOF &amp;gt;./kustomization.yaml
resources:
- deployment.yaml
images:
- name: nginx
  newName: my.image.registry/nginx
  newTag: 1.4.0
EOF&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1598774325524&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;gt; kubectl kustomize /kube-kustomize/kustomize-patch-images

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-nginx
spec:
  replicas: 2
  selector:
    matchLabels:
      run: my-nginx
  template:
    metadata:
      labels:
        run: my-nginx
    spec:
      containers:
        - image: my.image.registry/nginx:1.4.0
          name: my-nginx
          ports:
            - containerPort: 80&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;&lt;a id=&quot;user-content-baseoverlay&quot; href=&quot;https://github.com/yoonyeoseong/kubernetes-sample/tree/master/kube-kustomize#baseoverlay&quot; aria-hidden=&quot;true&quot;&gt;&lt;/a&gt;&lt;i&gt;&lt;b&gt;Base&amp;amp;Overlay&lt;/b&gt;&lt;/i&gt;&lt;/h2&gt;
&lt;p&gt;Kustomize는 base와 overlay의 개념을 가지고 있다. base는 kustomization.yaml과 함께 사용되는 디렉터리다. 이는 사용자 정의와 관련된 리소스들의 집합을 포함한다. kustomization.yaml의 내부에 표시되는 base는 로컬 디렉터리이거나 원격 리포지터리의 디렉터리가 될 수 있다. overlay는 kustomization.yaml이 있는 디렉터리로 다른 kustomization 디렉터리들을 bases로 참조한다. base는 overlay에 대해서 알지 못하며 여러 overlay들에서 사용될 수 있다. 한 overlay는 다수의 base들을 가질 수 있고, base들에서 모든 리소스를 구성할 수 있으며, 이들의 위에 사용자 정의도 가질 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1598774349764&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;object&quot; data-og-title=&quot;yoonyeoseong/kubernetes-sample&quot; data-og-description=&quot;Kubernetes(쿠버네티스) sample. Contribute to yoonyeoseong/kubernetes-sample development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/yoonyeoseong/kubernetes-sample/tree/master/kube-kustomize/base-and-overlay&quot; data-og-url=&quot;https://github.com/yoonyeoseong/kubernetes-sample&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cAo7Jz/hyHhTDX0rf/4qS7phlMqHw7s3qx4MEILk/img.jpg?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400,https://scrap.kakaocdn.net/dn/ejs4FR/hyHlbYHij5/bNqKaojiW6ezGvZVKarK70/img.png?width=1211&amp;amp;height=630&amp;amp;face=0_0_1211_630,https://scrap.kakaocdn.net/dn/76Orh/hyHjW28EwL/m2fo22DXsUqBZRswUl6lJk/img.png?width=696&amp;amp;height=668&amp;amp;face=0_0_696_668&quot;&gt;&lt;a href=&quot;https://github.com/yoonyeoseong/kubernetes-sample/tree/master/kube-kustomize/base-and-overlay&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/yoonyeoseong/kubernetes-sample/tree/master/kube-kustomize/base-and-overlay&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cAo7Jz/hyHhTDX0rf/4qS7phlMqHw7s3qx4MEILk/img.jpg?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400,https://scrap.kakaocdn.net/dn/ejs4FR/hyHlbYHij5/bNqKaojiW6ezGvZVKarK70/img.png?width=1211&amp;amp;height=630&amp;amp;face=0_0_1211_630,https://scrap.kakaocdn.net/dn/76Orh/hyHjW28EwL/m2fo22DXsUqBZRswUl6lJk/img.png?width=696&amp;amp;height=668&amp;amp;face=0_0_696_668');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;yoonyeoseong/kubernetes-sample&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;Kubernetes(쿠버네티스) sample. Contribute to yoonyeoseong/kubernetes-sample development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;pre id=&quot;code_1598774373336&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# base를 가지는 디렉터리 생성
mkdir base
# base/deployment.yaml 생성
cat &amp;lt;&amp;lt;EOF &amp;gt; base/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-nginx
spec:
  selector:
    matchLabels:
      run: my-nginx
  replicas: 2
  template:
    metadata:
      labels:
        run: my-nginx
    spec:
      containers:
      - name: my-nginx
        image: nginx
EOF

# base/service.yaml 파일 생성
cat &amp;lt;&amp;lt;EOF &amp;gt; base/service.yaml
apiVersion: v1
kind: Service
metadata:
  name: my-nginx
  labels:
    run: my-nginx
spec:
  ports:
  - port: 80
    protocol: TCP
  selector:
    run: my-nginx
EOF
# base/kustomization.yaml 생성
cat &amp;lt;&amp;lt;EOF &amp;gt; base/kustomization.yaml
resources:
- deployment.yaml
- service.yaml
EOF&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이 base는 다수의 overlay에서 사용될 수 있다. 다른 namePrefix 또는 다른 교차 편집 필드들을 서로 다른 overlay에 추가할 수 있다. 다음 예제는 동일한 base를 사용하는 두 overlay들이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1598774391224&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;gt; mkdir dev

cat &amp;lt;&amp;lt;EOF &amp;gt; dev/kustomization.yaml
#구버전 base 불러오는 방법
bases:
  - ../base
#resources:
#- ../base/kustomization.yaml

namespace: dev-my-nginx

patchesStrategicMerge:
  - patch-replica.yaml

patchesJson6902:
  - target:
      kind: Deployment
      name: my-nginx
      group: apps
      version: v1
    path: patch-resource.yaml

images:
  - name: nginx
    newName: my.image.registry/nginx
    newTag: 1.4.0
EOF

mkdir prod
cat &amp;lt;&amp;lt;EOF &amp;gt; prod/kustomization.yaml
#구버전 base 불러오는 방법
bases:
  - ../base
#resources:
#- ../base/kustomization.yaml

namespace: prod-my-nginx

patchesStrategicMerge:
  - patch-replica.yaml

patchesJson6902:
  - target:
      kind: Deployment
      name: my-nginx
      group: apps
      version: v1
    path: patch-resource.yaml

images:
  - name: nginx
    newName: my.image.registry/nginx
    newTag: 1.4.0
EOF&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;추가적으로 patch 파일들을 몇가지 작성하였다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1598774425649&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;gt; cd dev
&amp;gt; kubectl kustomize ./

#dev
apiVersion: v1
kind: Service
metadata:
  labels:
    run: my-nginx
  name: my-nginx
  namespace: dev-my-nginx
spec:
  ports:
    - port: 80
      protocol: TCP
  selector:
    run: my-nginx
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-nginx
  namespace: dev-my-nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      run: my-nginx
  template:
    metadata:
      labels:
        run: my-nginx
    spec:
      containers:
        - image: my.image.registry/nginx:1.4.0
          name: my-nginx
          ports:
            - containerPort: 80
          resources:
            limits:
              memory: 512Mi

&amp;gt; cd ../prod
&amp;gt; kubectl kustomize ./

#prod
apiVersion: v1
kind: Service
metadata:
  labels:
    run: my-nginx
  name: my-nginx
  namespace: prod-my-nginx
spec:
  ports:
    - port: 80
      protocol: TCP
  selector:
    run: my-nginx
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-nginx
  namespace: prod-my-nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      run: my-nginx
  template:
    metadata:
      labels:
        run: my-nginx
    spec:
      containers:
        - image: my.image.registry/nginx:1.4.0
          name: my-nginx
          ports:
            - containerPort: 80
          resources:
            limits:
              memory: 1024Mi&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;여기까지 쿠버네티스 설정 파일들을 관리하기 위한 방법으로 kustomize에 대해 간단히 다루어보었다.&lt;/p&gt;</description>
      <category>인프라/Docker&amp;amp;Kubernetes</category>
      <category>base overlays</category>
      <category>docker</category>
      <category>Helm</category>
      <category>Kubernetes</category>
      <category>kustomization</category>
      <category>kustomization.yaml</category>
      <category>kustomize</category>
      <category>patchesStrategicMerge</category>
      <category>patchsJson6902</category>
      <category>쿠버네티스</category>
      <author>여성게</author>
      <guid isPermaLink="true">https://coding-start.tistory.com/388</guid>
      <comments>https://coding-start.tistory.com/388#entry388comment</comments>
      <pubDate>Sun, 30 Aug 2020 17:02:22 +0900</pubDate>
    </item>
    <item>
      <title>Elasticsearch - 클러스터, 샤드, 인덱스 상태 확인하기</title>
      <link>https://coding-start.tistory.com/384</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rC221/btqHjqv8fnD/xJzkUPEZKejdt8uS145fWK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rC221/btqHjqv8fnD/xJzkUPEZKejdt8uS145fWK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rC221/btqHjqv8fnD/xJzkUPEZKejdt8uS145fWK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrC221%2FbtqHjqv8fnD%2FxJzkUPEZKejdt8uS145fWK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;오늘은 간단하게 클러스터 모니터링을 위한 API 몇개를 정리해본다.&lt;/p&gt;
&lt;p&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;http://es-host:9200/_cat/allocation?v -&amp;gt; 클러스터 디스크 현황&lt;/li&gt;
&lt;li&gt;http://es-host:9200/_cluster/health?pretty -&amp;gt; 클러스터 헬스체크&lt;/li&gt;
&lt;li&gt;http://es-host:9200/_cat/indices?v -&amp;gt; 인덱스 상태 확인&lt;/li&gt;
&lt;li&gt;http://es-host:9200/_cat/shards -&amp;gt; 모든 샤드 상태 확인&lt;/li&gt;
&lt;li&gt;http://es-host:9200/_cat/shards/{index_name}?v -&amp;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;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://brunch.co.kr/@alden/43&quot;&gt;https://brunch.co.kr/@alden/43&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1598324557149&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;article&quot; data-og-title=&quot;ElasticSearch status 바로 알기&quot; data-og-description=&quot;ElasticSearch | 오늘은 ElasticSearch (이하 ES)의 status 에 대한 이야기를 해볼까 합니다. ES의 status는 무엇을 의미하는지, 그리고 어떤 값들이 있으며 어떻게 확인할 수 있는지 살펴보겠습니다. ES status ��&quot; data-og-host=&quot;brunch.co.kr&quot; data-og-source-url=&quot;https://brunch.co.kr/@alden/43&quot; data-og-url=&quot;https://brunch.co.kr/@alden/43&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bXJnh0/hyHgHddEqO/xt5WEU6SU8rh2SvLkwWLOk/img.png?width=938&amp;amp;height=246&amp;amp;face=0_0_938_246,https://scrap.kakaocdn.net/dn/oRSvs/hyHbDvsOuQ/CWyWF6CPntAvTb72SChKE0/img.png?width=938&amp;amp;height=246&amp;amp;face=0_0_938_246&quot;&gt;&lt;a href=&quot;https://brunch.co.kr/@alden/43&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://brunch.co.kr/@alden/43&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bXJnh0/hyHgHddEqO/xt5WEU6SU8rh2SvLkwWLOk/img.png?width=938&amp;amp;height=246&amp;amp;face=0_0_938_246,https://scrap.kakaocdn.net/dn/oRSvs/hyHbDvsOuQ/CWyWF6CPntAvTb72SChKE0/img.png?width=938&amp;amp;height=246&amp;amp;face=0_0_938_246');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;ElasticSearch status 바로 알기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;ElasticSearch | 오늘은 ElasticSearch (이하 ES)의 status 에 대한 이야기를 해볼까 합니다. ES의 status는 무엇을 의미하는지, 그리고 어떤 값들이 있으며 어떻게 확인할 수 있는지 살펴보겠습니다. ES status ��&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;brunch.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Search-Engine/Elasticsearch&amp;amp;Solr</category>
      <author>여성게</author>
      <guid isPermaLink="true">https://coding-start.tistory.com/384</guid>
      <comments>https://coding-start.tistory.com/384#entry384comment</comments>
      <pubDate>Tue, 25 Aug 2020 12:02:40 +0900</pubDate>
    </item>
    <item>
      <title>Kubernetes - 쿠버네티스 클러스터 로깅(logging, fluentd + kafka + elk)</title>
      <link>https://coding-start.tistory.com/383</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cFvi8j/btqG6IZLpkU/lWAA4HnumKEGtuKwnVTH10/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cFvi8j/btqG6IZLpkU/lWAA4HnumKEGtuKwnVTH10/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cFvi8j/btqG6IZLpkU/lWAA4HnumKEGtuKwnVTH10/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcFvi8j%2FbtqG6IZLpkU%2FlWAA4HnumKEGtuKwnVTH10%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;이번 포스팅에서는 쿠버네티스 로깅 파이프라인 구성에 대해 다루어볼 것이다. 저번 포스팅에서는 Fluentd + ES + Kibana 조합으로 클러스터 로깅 시스템을 구성했었는데, 이번 시간에는 Fluentd + kafka + ELK 조합으로 구성해본다.&lt;/p&gt;
&lt;p&gt;&amp;lt;fluentd + ES + kibana logging&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1598269608573&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Kubernetes - Kubernetes 로깅 운영(logging), Fluentd&quot; data-og-description=&quot;오늘 다루어볼 내용은 쿠버네티스 환경에서의 로깅운영 방법이다. 지금까지는 쿠버네티스에 어떻게 팟을 띄우는지에 대해 집중했다면 오늘 포스팅 내용은 운영단계의 내용이 될 것 같다. 사실 &quot; data-og-host=&quot;coding-start.tistory.com&quot; data-og-source-url=&quot;https://coding-start.tistory.com/322?category=761720&quot; data-og-url=&quot;https://coding-start.tistory.com/322&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/3oWI7/hyHgEtqkPI/qsPRkKziJAZp8hDt1cBlEk/img.png?width=800&amp;amp;height=416&amp;amp;face=0_0_800_416,https://scrap.kakaocdn.net/dn/bz2Ntd/hyHgytdDLX/KEihzhhN6p4GedR0zuoHKK/img.png?width=800&amp;amp;height=416&amp;amp;face=0_0_800_416,https://scrap.kakaocdn.net/dn/qD00i/hyHeVjfp3w/BanGgPvYs5v0xEBY5CK5ek/img.png?width=5120&amp;amp;height=812&amp;amp;face=0_0_5120_812&quot;&gt;&lt;a href=&quot;https://coding-start.tistory.com/322?category=761720&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://coding-start.tistory.com/322?category=761720&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/3oWI7/hyHgEtqkPI/qsPRkKziJAZp8hDt1cBlEk/img.png?width=800&amp;amp;height=416&amp;amp;face=0_0_800_416,https://scrap.kakaocdn.net/dn/bz2Ntd/hyHgytdDLX/KEihzhhN6p4GedR0zuoHKK/img.png?width=800&amp;amp;height=416&amp;amp;face=0_0_800_416,https://scrap.kakaocdn.net/dn/qD00i/hyHeVjfp3w/BanGgPvYs5v0xEBY5CK5ek/img.png?width=5120&amp;amp;height=812&amp;amp;face=0_0_5120_812');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;Kubernetes - Kubernetes 로깅 운영(logging), Fluentd&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;오늘 다루어볼 내용은 쿠버네티스 환경에서의 로깅운영 방법이다. 지금까지는 쿠버네티스에 어떻게 팟을 띄우는지에 대해 집중했다면 오늘 포스팅 내용은 운영단계의 내용이 될 것 같다. 사실&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;coding-start.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;중간에 카프카를 두는 이유는 여러가지가 있을 수 있을 것 같다. 첫번째 버퍼역할을 하기때문에 어느정도 파이프라인의 속도 조절이 가능하다. 두번째 로그를 카프카 큐에 담아두고, 여러 컨슈머 그룹이 각기의 목적으로 로그데이터를 사용가능하다. 바로 실습에 들어가보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-size=&quot;size14&quot; data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;i&gt;구성&lt;br /&gt;&lt;/i&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;스크린샷 2020-08-24 오후 10.00.46.png&quot; data-origin-width=&quot;1090&quot; data-origin-height=&quot;186&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sHiGv/btqG6HGuUSi/jMYetlTqGt5qRcuk3LeVr0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sHiGv/btqG6HGuUSi/jMYetlTqGt5qRcuk3LeVr0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sHiGv/btqG6HGuUSi/jMYetlTqGt5qRcuk3LeVr0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsHiGv%2FbtqG6HGuUSi%2FjMYetlTqGt5qRcuk3LeVr0%2Fimg.png&quot; data-filename=&quot;스크린샷 2020-08-24 오후 10.00.46.png&quot; data-origin-width=&quot;1090&quot; data-origin-height=&quot;186&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;구성은 위 그림과 같다. fluentd는 컨테이너 로그를 tail하고 있고, tail한 데이터를 카프카로 프로듀싱한다. 그리고 아웃풋으로 로그스태시로 보내고 로그 스태시는 엘라스틱서치에 색인을하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;실습이전에 본 실습에서 진행하는 예제중 카프카 구성과 엘라스틱서치의 구성은 별도로 옵션 튜닝 및 물리머신에 구성하는 것이 좋다. 필자는 구성의 편의를 위해 아무런 옵션을 튜닝하지 않은채 같은 쿠버네티스 클러스터에 카프카와 엘라스틱서치를 구성하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-size=&quot;size14&quot; data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;&lt;b&gt;kafka install &amp;amp; deploy on kubernetes unsing helm&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;figure id=&quot;og_1598274284417&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;object&quot; data-og-title=&quot;TheOpenCloudEngine/uEngine-cloud-k8s&quot; data-og-description=&quot;Contribute to TheOpenCloudEngine/uEngine-cloud-k8s development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/TheOpenCloudEngine/uEngine-cloud-k8s/wiki/Kafka-on-kubernetes&quot; data-og-url=&quot;https://github.com/TheOpenCloudEngine/uEngine-cloud-k8s&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bwKTh2/hyHgDuCHSB/4kjTVP2w6WLN28poYASSR1/img.png?width=84&amp;amp;height=84&amp;amp;face=0_0_84_84,https://scrap.kakaocdn.net/dn/dxeNpl/hyHgL7gaWd/zeHPGiKVDfLv5UxKzGYgyk/img.png?width=2606&amp;amp;height=1626&amp;amp;face=0_0_2606_1626&quot;&gt;&lt;a href=&quot;https://github.com/TheOpenCloudEngine/uEngine-cloud-k8s/wiki/Kafka-on-kubernetes&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/TheOpenCloudEngine/uEngine-cloud-k8s/wiki/Kafka-on-kubernetes&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bwKTh2/hyHgDuCHSB/4kjTVP2w6WLN28poYASSR1/img.png?width=84&amp;amp;height=84&amp;amp;face=0_0_84_84,https://scrap.kakaocdn.net/dn/dxeNpl/hyHgL7gaWd/zeHPGiKVDfLv5UxKzGYgyk/img.png?width=2606&amp;amp;height=1626&amp;amp;face=0_0_2606_1626');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;TheOpenCloudEngine/uEngine-cloud-k8s&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;Contribute to TheOpenCloudEngine/uEngine-cloud-k8s development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;i&gt;&lt;b&gt;&amp;lt;헬름 설치&amp;gt;&lt;/b&gt;&lt;/i&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1598274329865&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;gt; curl https://raw.githubusercontent.com/kubernetes/helm/master/scripts/get | bash
&amp;gt; kubectl --namespace kube-system create sa tiller
&amp;gt; kubectl create clusterrolebinding tiller --clusterrole cluster-admin --serviceaccount=kube-system:tiller
&amp;gt; helm init --service-account tiller
&amp;gt; helm repo update&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;위 명령어로 헬름을 다운로드 받는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;i&gt;&lt;b&gt;&amp;lt;카프카 헬름 차트 설치 및 배포&amp;gt;&lt;/b&gt;&lt;/i&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1598274384726&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;gt; kubectl create ns kafka
&amp;gt; helm repo add incubator http://storage.googleapis.com/kubernetes-charts-incubator
&amp;gt; helm install --name my-kafka --namespace kafka incubator/kafka&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;kafka라는 별도의 네임스페이스를 생성하여 그 안에 카프카를 배포하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;i&gt;&lt;b&gt;&amp;lt;헬름차트 삭제&amp;gt;&lt;/b&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;차트 삭제가 필요하면 아래 명령어를 이용하자.&lt;/p&gt;
&lt;pre id=&quot;code_1598274420760&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# --purge 옵션으로 관련된 모든 정보를 지운다. 
helm delete my-kafka --purge&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;i&gt;&lt;b&gt;&amp;lt;fluentd가 데이터를 보낼 토픽생성&amp;gt;&lt;/b&gt;&lt;/i&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1598274468999&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;gt; kubectl -n kafka exec my-kafka-0 -- /usr/bin/kafka-topics \
--zookeeper my-kafka-zookeeper:2181 --topic fluentd-container-logging \
--create --partitions 3 --replication-factor 3

Created topic &quot;fluentd-container-logging&quot;.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&quot;fluentd-container-logging&quot;이라는 이름으로 토픽을 생성하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;lt;생성된 topic 확인&amp;gt;&lt;/p&gt;
&lt;pre id=&quot;code_1598274510152&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;gt; kubectl -n kafka exec my-kafka-0 -- /usr/bin/kafka-topics --zookeeper my-kafka-zookeeper:2181 --list

fluentd-container-logging&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;토픽리스트를 조회해서 우리가 생성한 토픽이 있는지 조회해본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;i&gt;&lt;b&gt;&amp;lt;fluentd가 보낸 데이터가 큐로 잘들어오는지 확인하기 위해 컨슘머 실행&amp;gt;&lt;/b&gt;&lt;/i&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1598274590095&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;gt; kubectl -n kafka exec -ti my-kafka-0 -- /usr/bin/kafka-console-consumer \
--bootstrap-server my-kafka:9092 --topic fluentd-container-logging --from-beginning&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;이제 실제로 카프카와 주키퍼가 쿠버네티스에 잘 떠있는지 확인해보자 !&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1598274636400&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;gt; kubectl get pod,svc -n kafka
  NAME                       READY   STATUS    RESTARTS   AGE
  pod/my-kafka-0             1/1     Running   2          4m14s
  pod/my-kafka-1             1/1     Running   0          116s
  pod/my-kafka-2             1/1     Running   0          78s
  pod/my-kafka-zookeeper-0   1/1     Running   0          4m14s
  pod/my-kafka-zookeeper-1   1/1     Running   0          3m32s
  pod/my-kafka-zookeeper-2   1/1     Running   0          3m
  NAME                                  TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)                      AGE
  service/my-kafka                      ClusterIP   10.108.104.66   &amp;lt;none&amp;gt;        9092/TCP                     4m14s
  service/my-kafka-headless             ClusterIP   None            &amp;lt;none&amp;gt;        9092/TCP                     4m14s
  service/my-kafka-zookeeper            ClusterIP   10.97.205.63    &amp;lt;none&amp;gt;        2181/TCP                     4m14s
  service/my-kafka-zookeeper-headless   ClusterIP   None            &amp;lt;none&amp;gt;        2181/TCP,3888/TCP,2888/TCP   4m14s&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;위와 같이 팟과 서비스 목록이 보인다면 다음으로 넘어간다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-size=&quot;size14&quot; data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;&lt;b&gt;ELK Stack 구성&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;i&gt;&lt;b&gt;&amp;lt;elasticsearch 실행&amp;gt;&lt;/b&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;아래 deployment와 service 설정파일을 이용하여 쿠버네티스 위에 엘라스틱서치를 구성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1598274744355&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Service
metadata:
  name: elasticsearch
  namespace: elk-stack
spec:
  selector:
    app: elasticsearch
  ports:
    - port: 9200
      protocol: TCP
      targetPort: 9200
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: elasticsearch
  namespace: elk-stack
  labels:
    app: elasticsearch
spec:
  replicas: 1
  selector:
    matchLabels:
      app: elasticsearch
  template:
    metadata:
      labels:
        app: elasticsearch
    spec:
      containers:
      - name: elasticsearch
        image: elastic/elasticsearch:6.8.6
        ports:
        - containerPort: 9200
          name: http
        - containerPort: 9300
          name: tcp&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;위 설정 파일은 볼륨을 구성하지 않아서 일회성(테스트)로만 가능하다. 실제로 운영환경에서는 물리머신에 클러스터를 구성하던가, 혹은 쿠버네티스 볼륨을 붙여서 구성하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1598274809335&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;gt; kubectl apply -f ./kube-logging/fluentd-elasticsearch/elasticsearch.yaml
&amp;gt; kubectl get pod,svc -n elk-stack
  NAME                                 READY   STATUS    RESTARTS   AGE
  pod/elasticsearch-654c5b6b77-l8k2z   1/1     Running   0          50s
  NAME                    TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE
  service/elasticsearch   ClusterIP   10.101.27.73   &amp;lt;none&amp;gt;        9200/TCP   50s&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;i&gt;&lt;b&gt;&amp;lt;kibana 실행&amp;gt;&lt;/b&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;키바나는 아래 설정파일을 예제로 구성하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1598274917999&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Service
metadata:
  name: kibana
  namespace: elk-stack
spec:
  selector:
    app: kibana
  ports:
  - protocol: TCP
    port: 5601
    targetPort: 5601
  type: NodePort
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: kibana
  namespace: elk-stack
  labels:
    app: kibana
spec:
  replicas: 1
  selector:
    matchLabels:
      app: kibana
  template:
    metadata:
      labels:
        app: kibana
    spec:
      containers:
      - name: kibana
        image: elastic/kibana:6.8.6
        ports:
        - containerPort: 5601
          name: http&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;위 설정중 조금 살펴봐야할 것은 서비스 타입을 NodePort로 준 점이다. 실제로 외부로 포트를 개방해 localhost로 접근 가능하다. 실제 운영환경에서는 ingress까지 구성하여 배포하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1598275007357&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;gt; kubectl apply -f ./kube-logging/fluentd-elasticsearch/kibana.yaml
&amp;gt; kubectl get pod,svc -n elk-stack | grep kibana
  NAME                                 READY   STATUS    RESTARTS   AGE
  pod/kibana-6d474df8c6-fsfc7          1/1     Running   0          24s
  NAME                                 READY   STATUS    RESTARTS   AGE
  service/kibana          NodePort    10.97.240.55   &amp;lt;none&amp;gt;        5601:30578/TCP   24s&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;http://localhost:30578로 접근해 키바나가 잘 떠있는지와 엘라스틱서치와 잘 연동되었는지 확인하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;i&gt;&lt;b&gt;&amp;lt;logstash 실행&amp;gt;&lt;/b&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;로그스태시는 아래 예시 설정 파일로 구성하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1598275131323&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apiVersion: v1
kind: ConfigMap
metadata:
  name: logstash-configmap
  namespace: elk-stack
data:
  logstash.yml: |
    http.host: &quot;127.0.0.1&quot;
    path.config: /usr/share/logstash/pipeline
    pipeline.workers: 2
  logstash.conf: |
    # all input will come from filebeat, no local logs
    input {
      kafka {
        bootstrap_servers =&amp;gt; &quot;my-kafka.kafka.svc.cluster.local:9092&quot;
        topics =&amp;gt; &quot;fluentd-container-logging&quot;
        group_id =&amp;gt; &quot;fluentd-consumer-group&quot;
        enable_auto_commit =&amp;gt; &quot;true&quot;
        auto_offset_reset =&amp;gt; &quot;latest&quot;
        consumer_threads =&amp;gt; 4
        codec =&amp;gt; &quot;json&quot;
      }
    }

    output {
        elasticsearch {
          hosts =&amp;gt; [&quot;http://elasticsearch.elk-stack.svc.cluster.local:9200&quot;]
          manage_template =&amp;gt; false
          index =&amp;gt; &quot;kubernetes-container-log-%{+YYYY-MM-dd}&quot;
        }
    }
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: logstash-deployment
  namespace: elk-stack
spec:
  replicas: 1
  selector:
    matchLabels:
      app: logstash
  template:
    metadata:
      labels:
        app: logstash
    spec:
      containers:
        - name: logstash
          image: docker.elastic.co/logstash/logstash:5.6.0
          ports:
            - containerPort: 5044
          volumeMounts:
            - name: config-volume
              mountPath: /usr/share/logstash/config
            - name: logstash-pipeline-volume
              mountPath: /usr/share/logstash/pipeline
      volumes:
        - name: config-volume
          configMap:
            name: logstash-configmap
            items:
              - key: logstash.yml
                path: logstash.yml
        - name: logstash-pipeline-volume
          configMap:
            name: logstash-configmap
            items:
              - key: logstash.conf
                path: logstash.conf
---
apiVersion: v1
kind: Service
metadata:
  name: logstash-service
  namespace: elk-stack
spec:
  selector:
    app: logstash
  ports:
    - protocol: TCP
      port: 5044
      targetPort: 5044
  type: ClusterIP
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;설정에서 잘 살펴볼 것은 input과 output의 호스트 설정이다. 우리는 모든 모듈을 같은 클러스터에 설치할 것이기 때문에 쿠버네티스 내부 DNS를 사용하였다.(실습에 편의를 위한 것이기도 하지만, 실제 운영환경에서도 내부 시스템은 종종 클러스터 내부 DNS를 사용하기도 한다. 그러면 실제로 통신하기 위해 클러스터 밖으로 나갔다 오지 않는다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;또 한가지 설정은 Deployment에 볼륨을 마운트 하는 부분이다. 실제 쿠버네티스에서 ConfigMap은 볼륨으로 잡히기 때문에 그 ConfigMap을 logstash pod 내부로 마운트하여 실행시점에 해당 설정파일을 물고 올라가도록 하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1598275311242&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;gt; kubectl apply -f ./kube-logging/fluentd-elasticsearch/logstash.yaml
&amp;gt; kubectl get pod,svc -n elk-stack | grep logstash
  NAME                                       READY   STATUS    RESTARTS   AGE  
  pod/logstash-deployment-556cfb66b5-6xrs6   1/1     Running   0          34s
  service/logstash-service   ClusterIP   10.96.13.170   &amp;lt;none&amp;gt;        5044/TCP         33s&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;i&gt;&lt;b&gt;&amp;lt;fluentd 실행&amp;gt;&lt;/b&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;이제는 실제 컨테이너 로그를 tail하여 수집하는 fluentd를 실행시켜보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1598275354037&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: fluentd
  namespace: kube-system
  labels:
    app: fluentd-logging
    version: v1
    kubernetes.io/cluster-service: &quot;true&quot;
spec:
  selector:
    matchLabels:
      app: fluentd-logging
  template:
    metadata:
      labels:
        app: fluentd-logging
        version: v1
        kubernetes.io/cluster-service: &quot;true&quot;
    spec:
      tolerations:
        - key: node-role.kubernetes.io/master
          effect: NoSchedule
      containers:
        - name: fluentd
          image: 1223yys/fluentd-kafka:latest
          imagePullPolicy: Always
          env:
            - name: FLUENT_KAFKA_BROKERS
              value: &quot;my-kafka.kafka.svc.cluster.local:9092&quot;
            - name: FLUENT_KAFKA_DEFAULT_TOPIC
              value: &quot;fluentd-container-logging&quot;
            - name: FLUENT_KAFKA_OUTPUT_DATA_TYPE
              value: &quot;json&quot;
            - name: FLUENT_KAFKA_COMPRESSION_CODEC
              value: &quot;snappy&quot;
            - name: FLUENT_KAFKA_MAX_SEND_LIMIT_BYTES
              value: &quot;4096&quot;
          resources:
            limits:
              memory: 200Mi
            requests:
              cpu: 100m
              memory: 200Mi
          volumeMounts:
            - name: varlog
              mountPath: /var/log
            - name: varlibdockercontainers
              mountPath: /var/lib/docker/containers
              readOnly: true
      terminationGracePeriodSeconds: 30
      volumes:
        - name: varlog
          hostPath:
            path: /var/log
        - name: varlibdockercontainers
          hostPath:
            path: /var/lib/docker/containers&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;fluentd 설정파일은 몇가지 짚고 넘어갈 것들이 있다. 첫번째는 컨테이너를 tail하기 위해 마운트한 설정이다. /var/log, /var/lib/docker/container를 마운트하였다. 실제 호스트머신에 해당 디렉토리에 들어가면 파일이 보이지 않을 것이다. 만약 파일을 보고 싶다면 아래 설정을 통해 도커 컨테이너를 실행시키고 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1598275580397&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;gt; docker run -it --rm -v /var/lib/docker/containers:/json-log alpine ash&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;위 도커이미지를 실행한후 /json-log 디렉토리에 들어가면 호스트머신에 쌓인 컨테이너 로그들을 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;두번째, tail한 로그를 내보내기 위한 env 설정이다. 아웃풋은 카프카로 두었고, 역시 도메인은 내부 클러스터 DNS로 잡아주었다. 그리고, 우리가 미리 생성한 토픽에 데이터를 보내고 있고 타입은 json으로 보내고 있다.(사실상 튜닝할 설정은 많지만 실습의 편의를 위해 대부분 기본 설정으로 잡았다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;그리고 필자가 fluentd 이미지를 새로 빌드한 이유는 카프카로 보내는 로그 포맷을 수정하기 위하여 fluentd 설정파일들을 조금 수정하였기 때문이다. 혹시나 fluentd 설정 파일들이 궁금하다면 포스팅 마지막 Github을 참조하자.(&lt;a href=&quot;https://github.com/yoonyeoseong/kubernetes-sample/tree/master/kube-logging/fluentd-kafka&quot;&gt;https://github.com/yoonyeoseong/kubernetes-sample/tree/master/kube-logging/fluentd-kafka&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1598275819429&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;gt; kubectl apply -f ./kube-logging/fluentd-kafka/fluentd-kafka-daemonset.yaml
&amp;gt; kubectl get pod,daemonset -n kube-system | grep fluentd
  NAME                                         READY   STATUS    RESTARTS   AGE
  pod/fluentd-bqmnl                            1/1     Running   0          34s
  daemonset.extensions/fluentd      1         1         1       1            1           &amp;lt;none&amp;gt;                        34s&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;이제 로그 출력을 위해 샘플 앱을 실행시켜보자. 로그 출력을 위한 앱은 꼭 아래 필자가 빌드한 웹 어플리케이션을 실행시킬 필요는 없다. 만약 아래 애플리케이션을 실행시키려면 ingress 설정 혹은 service node port를 설정하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1598275846607&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;gt; kubectl apply -f ./kube-resource/deployment-sample.yaml
&amp;gt; kubectl get pod
  NAME                                 READY   STATUS    RESTARTS   AGE
  sample-deployment-5fbf569554-4pzrf   0/1     Running   0          17s&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;이제 요청을 보내보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1598275917650&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;gt; kubectl get svc -n ingress-nginx
  NAME                                 TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)                      AGE
  ingress-nginx-controller             NodePort    10.97.27.106   &amp;lt;none&amp;gt;        80:30431/TCP,443:31327/TCP   21d
  ingress-nginx-controller-admission   ClusterIP   10.96.76.113   &amp;lt;none&amp;gt;        443/TCP                      21d
&amp;gt; curl localhost:30431/api&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;이제 키바나에 접속해보면 앱에서 출력하고 있는 로그 데이터를 볼 수 있다. 모든 예제 설정 및 코드는 아래 깃헙을 참고하자 !&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1598276095780&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;object&quot; data-og-title=&quot;yoonyeoseong/kubernetes-sample&quot; data-og-description=&quot;Kubernetes(쿠버네티스) sample. Contribute to yoonyeoseong/kubernetes-sample development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/yoonyeoseong/kubernetes-sample/tree/master/kube-logging&quot; data-og-url=&quot;https://github.com/yoonyeoseong/kubernetes-sample&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/zEKTA/hyHgIQf0QA/N3TfhjaEFk5ui5h2kJXZL0/img.jpg?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400,https://scrap.kakaocdn.net/dn/IvoI9/hyHgy7X5pW/maQ7ofOuxdHzZObU5xwzl0/img.png?width=1211&amp;amp;height=630&amp;amp;face=0_0_1211_630&quot;&gt;&lt;a href=&quot;https://github.com/yoonyeoseong/kubernetes-sample/tree/master/kube-logging&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/yoonyeoseong/kubernetes-sample/tree/master/kube-logging&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/zEKTA/hyHgIQf0QA/N3TfhjaEFk5ui5h2kJXZL0/img.jpg?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400,https://scrap.kakaocdn.net/dn/IvoI9/hyHgy7X5pW/maQ7ofOuxdHzZObU5xwzl0/img.png?width=1211&amp;amp;height=630&amp;amp;face=0_0_1211_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;yoonyeoseong/kubernetes-sample&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;Kubernetes(쿠버네티스) sample. Contribute to yoonyeoseong/kubernetes-sample development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>인프라/Docker&amp;amp;Kubernetes</category>
      <category>cluster logging</category>
      <category>ElasticSearch</category>
      <category>Elk</category>
      <category>fluentd</category>
      <category>Kafka</category>
      <category>kibana</category>
      <category>Kubernetes</category>
      <category>log pipeline</category>
      <category>logstash</category>
      <category>쿠버네티스</category>
      <author>여성게</author>
      <guid isPermaLink="true">https://coding-start.tistory.com/383</guid>
      <comments>https://coding-start.tistory.com/383#entry383comment</comments>
      <pubDate>Mon, 24 Aug 2020 22:35:27 +0900</pubDate>
    </item>
    <item>
      <title>Web Server - Nginx 설치 및 사용방법(nginx cache, reverse proxy, 프록시, 캐시)</title>
      <link>https://coding-start.tistory.com/381</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NKlHk/btqG5LnYiUW/gB297mZpHEzmje7hCXuHzK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NKlHk/btqG5LnYiUW/gB297mZpHEzmje7hCXuHzK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NKlHk/btqG5LnYiUW/gB297mZpHEzmje7hCXuHzK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNKlHk%2FbtqG5LnYiUW%2FgB297mZpHEzmje7hCXuHzK%2Fimg.jpg&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;오늘 포스팅해볼 내용은 Web server 중 하나인 Nginx의 설치 및 사용방법에 대해 다루어본다. 우선 Nginx는 무엇인가 알아보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;예제 설정은 아래 깃헙사이트에 있다.&lt;/p&gt;
&lt;figure id=&quot;og_1598079814468&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;object&quot; data-og-title=&quot;yoonyeoseong/kubernetes-sample&quot; data-og-description=&quot;Kubernetes(쿠버네티스) sample. Contribute to yoonyeoseong/kubernetes-sample development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/yoonyeoseong/kubernetes-sample/tree/master/nginx&quot; data-og-url=&quot;https://github.com/yoonyeoseong/kubernetes-sample&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/caHiNw/hyHdxCDEh0/kJIg0Zi9FVXS5nb047tbb0/img.jpg?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400,https://scrap.kakaocdn.net/dn/LXcNZ/hyHdrvDrK7/4XGSsEaxhlDD6VIXE5Czrk/img.png?width=1211&amp;amp;height=630&amp;amp;face=0_0_1211_630,https://scrap.kakaocdn.net/dn/5MS4n/hyHdBLLXvD/xRIzhBVRGLa9jeivzlQB01/img.png?width=696&amp;amp;height=668&amp;amp;face=0_0_696_668&quot;&gt;&lt;a href=&quot;https://github.com/yoonyeoseong/kubernetes-sample/tree/master/nginx&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/yoonyeoseong/kubernetes-sample/tree/master/nginx&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/caHiNw/hyHdxCDEh0/kJIg0Zi9FVXS5nb047tbb0/img.jpg?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400,https://scrap.kakaocdn.net/dn/LXcNZ/hyHdrvDrK7/4XGSsEaxhlDD6VIXE5Czrk/img.png?width=1211&amp;amp;height=630&amp;amp;face=0_0_1211_630,https://scrap.kakaocdn.net/dn/5MS4n/hyHdBLLXvD/xRIzhBVRGLa9jeivzlQB01/img.png?width=696&amp;amp;height=668&amp;amp;face=0_0_696_668');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;yoonyeoseong/kubernetes-sample&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;Kubernetes(쿠버네티스) sample. Contribute to yoonyeoseong/kubernetes-sample development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;i&gt;&lt;b&gt;Wiki(&lt;a href=&quot;https://ko.wikipedia.org/wiki/Nginx&quot;&gt;https://ko.wikipedia.org/wiki/Nginx&lt;/a&gt;)&lt;/b&gt;&lt;/i&gt;&lt;br /&gt;Nginx(엔진 x라 읽는다)는 웹 서버 소프트웨어로, 가벼움과 높은 성능을 목표로 한다. 웹 서버, 리버스 프록시 및 메일 프록시 기능을 가진다.&lt;br /&gt;2017년 10월 기준으로 실질적으로 작동하는 웹 사이트(active site)들에서 쓰이는&amp;nbsp;웹 서버 소프트웨어&amp;nbsp;순위는&amp;nbsp;아파치(44.89%), 엔진엑스(20.65%),&amp;nbsp;구글 웹 서버(7.86%), 마이크로소프트&amp;nbsp;IIS(7.32%)순이다.[1]&amp;nbsp;이 조사에서 생성은 되어있으나 정상적으로 작동하지 않는 웹 사이트들은 배제되었으며[2]&amp;nbsp;특히 MS의&amp;nbsp;인터넷 정보 서비스(IIS)를 설치한 웹 사이트들의 상당수가 비활성 사이트였다. 그런 사이트들도 포함하면 MS IIS가 1위이다. 2017년 6월 현재 Nginx는 한국 전체 등록 도메인 중 24.73%가 사용하고 있다.[3]&lt;br /&gt;Nginx는 요청에 응답하기 위해 비동기&amp;nbsp;이벤트 기반&amp;nbsp;구조를 가진다. 이것은&amp;nbsp;아파치 HTTP 서버의 스레드/프로세스 기반 구조를 가지는 것과는 대조적이다. 이러한 구조는 서버에 많은 부하가 생길 경우의 성능을 예측하기 쉽게 해준다.&lt;/blockquote&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;또한 nginx는 하나의 마스터 프로세스와 여러 워커 프로세스가 있고, 마스터 프로세스는 주로 설정 파일을 읽고 적용하며 워커 프로세스들을 관리하는 역할을 하게 된다. 워커 프로세스는 실제 요청에 대한 처리를 하게 된다. nginx는 event driven 모델을 메커니즘으로 사용하여 실제 워커 프로세스간 요청을 효율적으로 분산한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;실습은 Mac os 기준으로 실습을 진행해 볼것이다. 우선 nginx를 설치해보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;&lt;b&gt;Nginx install&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;pre id=&quot;code_1598067273055&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;gt; brew install nginx&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;brew로 설치를 아래와 같은 디렉터리들이 생성된다. 우선 아래 디렉토리를 실습을 진행하면서 전부 알아볼 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1598067350038&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Docroot is: /usr/local/var/www

The default port has been set in /usr/local/etc/nginx/nginx.conf to 8080 so that
nginx can run without sudo.

nginx will load all files in /usr/local/etc/nginx/servers/.

To have launchd start nginx now and restart at login:
  brew services start nginx
Or, if you don't want/need a background service you can just run:
  nginx
==&amp;gt; Summary
   /usr/local/Cellar/nginx/1.19.2: 25 files, 2.1MB
==&amp;gt; Caveats
==&amp;gt; nginx
Docroot is: /usr/local/var/www

The default port has been set in /usr/local/etc/nginx/nginx.conf to 8080 so that
nginx can run without sudo.

nginx will load all files in /usr/local/etc/nginx/servers/.

To have launchd start nginx now and restart at login:
  brew services start nginx
Or, if you don't want/need a background service you can just run:
  nginx&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;&lt;b&gt;Nginx 구동 명령어(nginx -s &amp;lt;signal&amp;gt;&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;nginx : 서버시작&lt;/li&gt;
&lt;li&gt;nginx -s stop : 서버종료(워커들이 요청을 처리중이더라도 그냥 종료한다.)&lt;/li&gt;
&lt;li&gt;nginx -s quit : 워커 프로세스가 현재 요청 처리를 완료할 때까지 대기하고 모두 처리완료된 후에 서버 종료.&lt;/li&gt;
&lt;li&gt;nginx -s reload : nginx config를 새로 로드한다. 마스터 프로세스가 설정을 다시 로드하라는 요청을 받으면 설정 유효성 검사후 새로운 워커 프로세스를 시작하고, 이전 워커 프로세스에게 종료 메시지를 보내게 되고 이전 워커 프로세스는 요청을 완료하게 되면 종료된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;위 명령어로 nginx를 시작 해보자 !&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1598067436392&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;gt; nginx
&amp;gt; lsof -i:8080
COMMAND   PID         USER   FD   TYPE             DEVICE SIZE/OFF NODE NAME
nginx   88891 yun-yeoseong    6u  IPv4 0x7370b7ed168f296f      0t0  TCP *:http-alt (LISTEN)
nginx   88892 yun-yeoseong    6u  IPv4 0x7370b7ed168f296f      0t0  TCP *:http-alt (LISTEN)
#실행중인 모든 nginx 프로세스 목록을 가져온다.
&amp;gt; ps -ax | grep nginx
88891 ??         0:00.00 nginx: master process nginx
88892 ??         0:00.01 nginx: worker process
89201 ttys000    0:00.03 vi nginx.conf
89695 ttys001    0:00.00 grep --color=auto --exclude-dir=.bzr --exclude-dir=CVS --exclude-dir=.git --exclude-dir=.hg --exclude-dir=.svn nginx&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;디폴트 포트인 8080으로 nginx 프로세스가 잘 떠있다. 이제 웹브라우저에서 localhost:8080으로 접속해보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1598067555237&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;gt; curl localhost:8080
&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;
&amp;lt;title&amp;gt;Welcome to nginx!&amp;lt;/title&amp;gt;
&amp;lt;style&amp;gt;
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
&amp;lt;/style&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
&amp;lt;h1&amp;gt;Welcome to nginx!&amp;lt;/h1&amp;gt;
&amp;lt;p&amp;gt;If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;For online documentation and support please refer to
&amp;lt;a href=&quot;http://nginx.org/&quot;&amp;gt;nginx.org&amp;lt;/a&amp;gt;.&amp;lt;br/&amp;gt;
Commercial support is available at
&amp;lt;a href=&quot;http://nginx.com/&quot;&amp;gt;nginx.com&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;&amp;lt;em&amp;gt;Thank you for using nginx.&amp;lt;/em&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;스크린샷 2020-08-22 오후 12.37.58.png&quot; data-origin-width=&quot;1084&quot; data-origin-height=&quot;412&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LjKTY/btqG2GHFBAi/SQVd8Jd7W7nggKKN5Ktvx0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LjKTY/btqG2GHFBAi/SQVd8Jd7W7nggKKN5Ktvx0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LjKTY/btqG2GHFBAi/SQVd8Jd7W7nggKKN5Ktvx0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLjKTY%2FbtqG2GHFBAi%2FSQVd8Jd7W7nggKKN5Ktvx0%2Fimg.png&quot; data-filename=&quot;스크린샷 2020-08-22 오후 12.37.58.png&quot; data-origin-width=&quot;1084&quot; data-origin-height=&quot;412&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;브라우저에 welcome to nginx가 보인다면 설치 및 실행이 잘된 것이다 ! 어 그렇다면, 여기서 조금 의아한 것이 있을 것이다. 과연 저 html은 어디서 응답을 준것일까?&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;&lt;b&gt;Docroot&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;p&gt;답은 도큐먼트 루트에 있다. 설치를 하면 아래와 같은 로그가 출력되어있을 것인데, 해당 디렉토리 내에 html 파일이 존재한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1598067701846&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Docroot is: /usr/local/var/www&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;기본적으로 웹서버는 다른 서버로 프록시 하지 않는 이상 uri로 명시한 path로 도큐먼트 루트 디렉토리를 찾아서 응답을 주게 된다. 사실 localhost:8080은 localhost:8080/index.html과 같다고 보면된다. 그렇다면 index.html의 위치를 바꾸면 어떻게 될까?&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1598067849974&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;gt; cd /usr/local/var/www
&amp;gt; mkdir backup
&amp;gt; mv index.html ./backup&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이제 아래 요청을 보내보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1598067916999&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;gt; curl localhost:8080/index.html
&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;&amp;lt;title&amp;gt;404 Not Found&amp;lt;/title&amp;gt;&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
&amp;lt;center&amp;gt;&amp;lt;h1&amp;gt;404 Not Found&amp;lt;/h1&amp;gt;&amp;lt;/center&amp;gt;
&amp;lt;hr&amp;gt;&amp;lt;center&amp;gt;nginx/1.19.2&amp;lt;/center&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;우리는 index.html을 다른 디렉토리로 옮겼기 때문에 404 not found가 뜨게 된다. 그렇다면 옮긴 디렉토리 path를 명시해서 요청을 보내보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1598067978649&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;gt; curl http://localhost:8080/backup/index.html
&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;
&amp;lt;title&amp;gt;Welcome to nginx!&amp;lt;/title&amp;gt;
&amp;lt;style&amp;gt;
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
&amp;lt;/style&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
&amp;lt;h1&amp;gt;Welcome to nginx!&amp;lt;/h1&amp;gt;
&amp;lt;p&amp;gt;If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;For online documentation and support please refer to
&amp;lt;a href=&quot;http://nginx.org/&quot;&amp;gt;nginx.org&amp;lt;/a&amp;gt;.&amp;lt;br/&amp;gt;
Commercial support is available at
&amp;lt;a href=&quot;http://nginx.com/&quot;&amp;gt;nginx.com&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;&amp;lt;em&amp;gt;Thank you for using nginx.&amp;lt;/em&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;응답이 잘 도착하였다. 보통 도큐멘트 루트는 어떻게 사용이 될까? 보통은 정적인 리소스 파일(css, html)을 위치시키게 된다. 그렇다면 정적인 리로스 파일을 위치시키는 이유는 무엇일까? 만약 WAS에 해당 정적인 리소스 파일을 위치시키게 되면, 사실상 서버 동작과 관련이 적은 정적 리소스를 가져오기 위한 요청도 모두 WAS로 들어가기 때문에 앱에 부하가 많이 가게 될수 있다. 그렇기 때문에 보통 정적인 리소스는 nginx(웹서버)에서 처리하고 WAS는 백엔드 데이터만 제공하게 하여 WAS의 부담을 줄여줄 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이제는 본격적으로 Nginx의 설정을 커스터마이징해보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;&lt;b&gt;Configuration file's structure&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;p&gt;nginx의 설정 파일은 simple directives(단순 지시문)과 block directives(블록 지시문)으로 나뉜다. 단순 지시문을 공백으로 구분 된 이름과 매개변수로 구성되며 세미콜론(;)으로 끝난다. 블록 지시문은 단순 지시문과 구조가 동일하지만 세미콜론 대신 중괄호({})로 명령 블록을 지정한다. 또한 블록지시문을 블록지시문의 중첩구조로도 이루어 질 수 있다. 이러한 지시문으로 nginx에 플러그인 된 여러 모듈을 제어하게 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;&lt;b&gt;Nginx Configuration&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;p&gt;nginx.conf 파일에는 nginx의 설정 내용이 들어간다. 해당 파일의 전체적인 구조(모듈)는 아래와 같이 이루어져있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1598071092541&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;user  nginx;
worker_processes  1;

error_log  logs/error.log;

events {
    worker_connections  1024;
}
http { 
    include       mime.types;
    #응답의 기본 default mime type을 지정
    default_type  application/octet-stream;
    
    charset utf-8;
    
    log_format  main  '$remote_addr - $remote_user [$time_local] &quot;$request&quot; '
    '$status $body_bytes_sent &quot;$http_referer&quot; '
    '&quot;$http_user_agent&quot; &quot;$http_x_forwarded_for&quot;';
    
    access_log  /var/log/nginx/access.log  main;
    
	#지정된 에러 코드에 대해 응답나갈 document root의 html 파일을 지정
    #docroot의 html말고 다른 URL로 리다이렉션 가능하다.
    error_page 500 502 503 504 /50x.html;
    #error_page 500 502 503 504 http://example.com/error.html
    
    sendfile        on;
    tcp_nopush     on;
    
    keepalive_timeout  65;
    #keepalive로 유지되는 커넥션으로 최대 처리할 요청수를 지정
    #keepalive_requests 100;    
    
    #nginx의 버전을 숨길 것인가에 대한 옵션이다. 보안상 활성화하는 것을 권장한다.
    server_tokens            on;
    #응답 컨텐츠를 압축하는 옵션, 해당 옵션말고 gzip관련 다양한 옵션 존재(압축 사이즈 등등)
    gzip  on;
    
    #context : http, server, location
    #클라이언트 요청 본문을 읽기 위한 버퍼 크기를 설정 64bit platform default 16k
    client_body_buffer_size 16k;
    #클라이언트 요청 본문을 읽기 위한 타임아웃 시간 설정
    client_body_timeout 60s;
    #클라이언트 요청 헤더를 읽기위한 버퍼 크기 설정
    client_header_buffer_size 1k;
    client_header_timeout 60s;
    #클라이언트가 보낸 요청 본문의 최대 사이즈
    client_max_body_size 1m;
    
    server {
        listen       80;
        location / {
            root   html;
            index  index.html index.htm;
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&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;b&gt;Core 모듈 설정&lt;/b&gt; : 위 예제의 worker_processes와 같은 지시자 설정 파일 최상단에 위치하면서 nginx의 기본적인 동작 방식을 정의한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;http 모듈 블록&lt;/b&gt; : 밑에서 설명할 server, location의 루트 블록이라고 할 수 있고, 여기서 설정된 값을 하위 블록들은 상속한다. http 블록은 여러개를 사용할 수 있지만 관리상의 이슈로 한번만 정의하는 것을 권장한다. http, server, location 블록은 계층구조를 가지고 있고 많은 지시어가 각각의 블록에서 동시에 사용될 수 있는데,&amp;nbsp; http의 내용은 server의 기본값이 되고, server의 지시어는 location의 기본값이 된다. 그리고 하위의 블록에서 선언된 지시어는 상위의 선언을 무시하고 적용된다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;server 블록&lt;/b&gt; : server 블록은 하나의 웹사이트를 선언하는데 사용된다. 가상 호스팅(vhost)의 개념이다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;location 블록&lt;/b&gt; : location 블록은 server 블록 안에 정의하며 특정 URL을 처리하는 방법을 정의한다. 예를 들어 uri path마다 다르게 요청을 처리하고 싶을 때 해당 블록 내에 정의한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;events 블록&lt;/b&gt; : nginx는 event driven을 메커니즘으로 동작하는데, 이 event driven 동작 방식에 대한 설정을 다룬다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;&lt;b&gt;nginx.conf&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;i&gt;&lt;b&gt;&quot;user&quot;&lt;/b&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p&gt;user의 값이 root로 되어 있다면 일반 계정으로 변경하는 것이 좋다. nginx는 마스터 프로세스와 워커 프로세스로 동작하고, 워커 프로세스가 실질적인 웹서버의 역할을 수행하는데 user 지시어는 워커프로세스의 권한을 지정한다. 만약 user의 값이 root로 되어 있다면 워커 프로세스를 root의 권한으로 동작하게 되고, 워커 프로세스를 악의적으로 사용자가 제어하게 된다면 해당 머신을 루트 사용자의 권한으로 원격제어하게 되는 셈이기 때문에 보안상 위험하다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;user 설정의 값으로는 대표성있는 이름(nginx)로 사용하고, 이 계정은 일반 유저의 권한으로 쉘에 접속할 수 없어야 안전하다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1598071668922&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;gt; useradd --shell /sbin/nologin www-data&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;i&gt;&lt;b&gt;&quot;worker_process&quot;&lt;/b&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p&gt;worker_process는 워커 프로세스를 몇개 생성할 것인지를 지정하는 지시어이다. 이 값이 1이라면 모든 요청을 하나의 프로세스로 실행하겠다는 뜻인데, 여러개의 CPU 코어가 있는 시스템이라면 CPU 코어수만큼 지정하길 권장한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;i&gt;&lt;b&gt;&quot;events.worker_connections&quot;&lt;/b&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p&gt;이 값은 몇개의 접속을 동시에 처리할 것인가를 지정하는 값이다. 이 값과 worker_process의 값을 조합해 동시에 최대로 처리할 수 있는 커넥션의 양을 산출할 수 있다.(worker_process*worker_connections)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;i&gt;&lt;b&gt;&quot;http.incloud&quot;&lt;/b&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p&gt;가상 호스트 설정이나, 반복되는 설정들을 파일로 저장해놓고, incloude를 통해 불러올 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;i&gt;&lt;b&gt;&quot;http.log_format&quot;&lt;/b&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p&gt;access 로그에 남길 로그 포맷을 지정한다. 보통 어떠한 장애가 났을 때, 가장 먼저보는 것이 로그 파일이기 때문에 디버깅하기 위해 유용한 값들을 로그에 남겨두는 것이 중요하다. 특히나, 여러 프록시 서버를 지나오는 서버 구성인 경우에는 x-forwarded-ip 등을 지정하면 지나온 프록시들의 아이피들을 할 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;i&gt;&lt;b&gt;&quot;http.access_log&quot;&lt;/b&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p&gt;access로그를 어느 디렉토리에 남길지 설정한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;i&gt;&lt;b&gt;&quot;http.keepalive_timeout&quot;&lt;/b&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p&gt;소켓을 끊지 않고 얼마나 유지할지에 대한 설정이다. 자세한 내용은 keepalive 개념을 확인하자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;i&gt;&lt;b&gt;&quot;http.server_tokens&quot;&lt;/b&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p&gt;nginx의 버전을 숨길 것인가에 대한 옵션이다. 보안상 활성화하는 것을 권장한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;기타 설정들은 위 예제 파일에 주석으로 달아놓았다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;다음은 실제 프록시 설정이 들어가는 server 블록 설정을 다루어 보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1598075346550&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;server {
    listen 80;
    server_name levi.local.com;
    access_log  logs/access.log;
    error_log   logs/error.log;
    error_page  500 502 503 504 /50x.html;
    charset     utf-8;
    
    location / {
    	proxy_pass  http://app;
    }
}

upstream app {
	server localhost:8080;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위 설정은 http 블록 하위로 들어가게 된다. 크게 어려운 설정은 없고, &quot;levi.local.com:80/&quot;으로 요청이 들어오면 upstream(요청받는 서버)으로 요청을 리버스 프록시 한다라는 뜻이다. 실제로 앱하나를 띄워보고 프록시 되는지 확인해보자.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1598076399180&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;gt; curl levi.local.com/api
new api ! - 7&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위처럼 응답이 잘오는 것을 볼 수 있다. 그런데 사실 server 블록이 하나일때는 server_name에 적혀있는 도메인으로 오지않아도 응답을 준다. server_name이 진짜 도메인네임을 구분하기 위한 server_name으로 사용되기 위해서는 listen 포트가 같은 server 블록이 두개 이상 존재할때 이다. 아래 예제를 보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1598076495928&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    server {
        listen 80;
        server_name levi.local.com;
        #access_log  logs/access.log;
        #error_log   logs/error.log;
        error_page  500 502 503 504 /50x.html;
        charset     utf-8;

        location / {
            proxy_pass  http://app;
        }
    }

    upstream app {
        server localhost:8080;
    }

    server {
        listen 80;
        server_name local.yoon.com;
        #access_log  logs/access.log;
        #error_log   logs/error.log;
        error_page  500 502 503 504 /50x.html;
        charset     utf-8;

        location / {
            proxy_pass  http://app2;
        }
    }

    upstream app2 {
        server localhost:7070;
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위와 같이 설정하고, 각 도메인을 분리해서 요청을 보내보자. server_name으로 분리되어 요청이 프록시 될것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;&lt;b&gt;Nginx cache&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;p&gt;마지막으로 location 블록에 대한 설정중 nginx cache에 설정에 대해 주로 다루어보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;/path/to/cache&lt;/b&gt; ==&amp;gt; 캐시 내용이 local disk 에 저장될 위치&lt;/li&gt;
&lt;li&gt;&lt;b&gt;levels=1:2&lt;/b&gt; ==&amp;gt; directory depth 와 사용할 name 길이.
&lt;ul&gt;
&lt;li&gt;ex ) /data/nginx/cache/&lt;span&gt;c&lt;/span&gt;/&lt;span&gt;29&lt;/span&gt;/b7f54b2df7773722d382f4809d650&lt;span&gt;29&lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;keys_zone&lt;/b&gt; ==&amp;gt; 캐시 키로 사용될 이름과 크기. 1MB 는 약 8천개의 이름 저장. 10MB면 8만개.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;max_size&lt;/b&gt; ==&amp;gt; 캐시 파일 크기의 maximum. size 가 over 되면 가장 오래전에 사용한 데이터 부터 삭제한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;inactive&lt;/b&gt; ==&amp;gt; access 되지 않았을 경우 얼마 뒤에 삭제 할 것인가.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;use_temp_path&lt;/b&gt; ==&amp;gt; 설정된 path 외에 임시 저장 폴더를 따로 사용할 것인가? 따로 설정하지 않는 것이 좋다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;proxy_cache &amp;lt;namev&amp;gt;&lt;/b&gt; ==&amp;gt; 캐시로 사용할 메모리 zone 이름.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;proxy_cache_methods&lt;/b&gt; ==&amp;gt; request method를 정의한다. default : GET, HEAD&lt;/li&gt;
&lt;li&gt;&lt;b&gt;proxy_cache_key&lt;/b&gt; ==&amp;gt; 캐시 할 때 사용할 이름.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;proxy_cache_bypass&lt;/b&gt; ==&amp;gt; 예를 들어 &quot;&lt;a href=&quot;http://www.example.com/?nocache=true&quot;&gt;http://www.example.com/?nocache=true&lt;/a&gt;&quot; 이러한 요청이 왔을 때 캐싱되지 않은 response 를 보낸다. 이 설정이 없다면 nocache 아규먼트는 동작하지 않는다. http_pragma==&amp;gt; 헤더 Pragma:no-cache&lt;/li&gt;
&lt;li&gt;&lt;b&gt;proxy_cache_lock&lt;/b&gt; ==&amp;gt; 활성화 시키면 한 번에 단 하나의 요청만 proxy server로 전달되어 proxy_cache_key 에 따라 캐싱된 데이터로 사용합니다. 다른 request 들은 캐싱된 데이터를 사용하거나 proxy_cache_lock_timeout의 설정에 따라 proxy server로 전달 될 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;proxy_cache_valid&lt;/b&gt;&amp;nbsp;==&amp;gt; 기본적으로 캐싱할 response code 와 시간을 정의한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;예제 설정으로는 아래와 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1598077569707&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;proxy_cache_path /usr/local/etc/nginx/cache levels=1:2 keys_zone=myapp:10m max_size=10g inactive=60s use_temp_path=off;

server {
    listen 80;
    server_name levi.local.com;
    access_log  logs/access.log;
    error_log   logs/error.log;
    error_page  500 502 503 504 /50x.html;
    charset     utf-8;
    
    location / {
        proxy_cache myapp;
        proxy_cache_methods GET;
        proxy_cache_key &quot;$uri$is_args$args&quot;;
        proxy_cache_bypass $cookie_nocache $arg_nocache $http_pragma;
        proxy_ignore_headers Expires Cache-Control Set-Cookie;
        #proxy_cache_lock on;
        #200ok인 응답을 1분동안 캐싱
        proxy_cache_valid 200 1m; 
        
        proxy_pass  http://app;
    }
}

upstream app {
	server localhost:8080;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;실제로 캐싱이 잘되는지 요청을 보내보고 실제 캐싱이 저장되는 디렉토리로 들어가보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1598078424956&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;gt; cd /usr/local/etc/nginx/cache
&amp;gt; ls
8
&amp;gt; cd 8
&amp;gt; ls
68
&amp;gt; cd 68
&amp;gt; ls
5d198634e5fa00f3cf3a478fcdf57688
&amp;gt; vi 5d198634e5fa00f3cf3a478fcdf57688
^E^@^@^@^@^@^@^@&amp;ucirc;&amp;frac12;@_^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@&amp;yuml;&amp;yuml;&amp;yuml;&amp;yuml;&amp;yuml;&amp;yuml;&amp;yuml;&amp;yuml;&amp;iquest;&amp;frac12;@_^@^@^@^@#Y|^V^@^@d^A&amp;egrave;^A^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@
KEY: /api?arg=args
HTTP/1.1 200 ^M
Content-Type: text/html;charset=UTF-8^M
Content-Length: 13^M
Date: Sat, 22 Aug 2020 06:39:59 GMT^M
Connection: close^M
^M
new api ! - 5&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;응답이 잘 캐싱된것을 볼수 있다. 그리고 대략 1분후에는 해당 캐싱 파일 지워져있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;여기까지 간단하게 Nginx 설치 및 사용방법에 대해 다루어보았다. 맘 같아선 캐싱에 대해 더 자세히 다루고 싶었다. 대규모 웹사이트 같은 경우는 정말 장비를 늘리는 것으로는 트래픽을 받는데 한계가 있기 때문에 사실상 캐싱 싸움이 될것이기 때문이다. 이번 포스팅에서는 Nginx에 대해 맛보기 정도만 하였지만, 다음 시간에는 조금더 딥한 내용까지 다루어 볼 계획이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;참조&lt;/p&gt;
&lt;figure id=&quot;og_1598079772909&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;article&quot; data-og-title=&quot;nginx cache&quot; data-og-description=&quot;1. cache dir 설정 proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=cache:2m 2. cache 사용 설정 server { listen &amp;nbsp;80; server_name cached.test.co.kr; access_log /var/log/nginx/cache-access.log c..&quot; data-og-host=&quot;semode.tistory.com&quot; data-og-source-url=&quot;https://semode.tistory.com/218&quot; data-og-url=&quot;https://semode.tistory.com/218&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/tCqJx/hyHeJ9hhT1/XomDDKwBOaqepdGVt8tcy1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/FvFNp/hyHbBKIcnj/xoJDv77VpV6IaLkHth5U3k/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800&quot;&gt;&lt;a href=&quot;https://semode.tistory.com/218&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://semode.tistory.com/218&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/tCqJx/hyHeJ9hhT1/XomDDKwBOaqepdGVt8tcy1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/FvFNp/hyHbBKIcnj/xoJDv77VpV6IaLkHth5U3k/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;nginx cache&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;1. cache dir 설정 proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=cache:2m 2. cache 사용 설정 server { listen &amp;nbsp;80; server_name cached.test.co.kr; access_log /var/log/nginx/cache-access.log c..&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;semode.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>인프라/Web Server &amp;amp; WAS</category>
      <category>nginx</category>
      <category>nginx cache</category>
      <category>proxy</category>
      <category>proxy server</category>
      <category>Reverse Proxy</category>
      <category>web server</category>
      <category>리버스 프록시</category>
      <category>엔진엑스</category>
      <category>웹서버</category>
      <category>프록시서버</category>
      <author>여성게</author>
      <guid isPermaLink="true">https://coding-start.tistory.com/381</guid>
      <comments>https://coding-start.tistory.com/381#entry381comment</comments>
      <pubDate>Sat, 22 Aug 2020 15:43:54 +0900</pubDate>
    </item>
    <item>
      <title>운영체제 - 디스크 사용량 및 정보 확인</title>
      <link>https://coding-start.tistory.com/380</link>
      <description>&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1598023394082&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;article&quot; data-og-title=&quot;UNIX/LINUX : 용량 확인 명령어 (df/du)&quot; data-og-description=&quot;unix/linux Unix/Linux 디스크 용량 확인 (df/du) 디스크 용량을 확인하는 명령어들이다. df : 디스크의 남은 용량을 확인 df -k : 킬로바이트 단위로 현재 남은 용량을 확인 df -m : 메가바이트 단위로 남은 �&quot; data-og-host=&quot;ra2kstar.tistory.com&quot; data-og-source-url=&quot;https://ra2kstar.tistory.com/135&quot; data-og-url=&quot;https://ra2kstar.tistory.com/135&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bQudTI/hyHduySesb/HgzniMK9SYwscW4QkVkEXK/img.jpg?width=260&amp;amp;height=81&amp;amp;face=0_0_260_81,https://scrap.kakaocdn.net/dn/sIDQf/hyHbymMm7o/kKVGSshbRi1iFXUVqanbk0/img.jpg?width=260&amp;amp;height=81&amp;amp;face=0_0_260_81&quot;&gt;&lt;a href=&quot;https://ra2kstar.tistory.com/135&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://ra2kstar.tistory.com/135&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bQudTI/hyHduySesb/HgzniMK9SYwscW4QkVkEXK/img.jpg?width=260&amp;amp;height=81&amp;amp;face=0_0_260_81,https://scrap.kakaocdn.net/dn/sIDQf/hyHbymMm7o/kKVGSshbRi1iFXUVqanbk0/img.jpg?width=260&amp;amp;height=81&amp;amp;face=0_0_260_81');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;UNIX/LINUX : 용량 확인 명령어 (df/du)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;unix/linux Unix/Linux 디스크 용량 확인 (df/du) 디스크 용량을 확인하는 명령어들이다. df : 디스크의 남은 용량을 확인 df -k : 킬로바이트 단위로 현재 남은 용량을 확인 df -m : 메가바이트 단위로 남은 �&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;ra2kstar.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-size=&quot;size14&quot; data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;&lt;b&gt;디스크의 남은 용량 확인&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;&lt;span&gt;df -k&lt;/span&gt;&lt;/b&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 킬로바이트 단위로 현재 남은 용량을 확인&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span&gt;df -m&lt;/span&gt;&lt;/b&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 메가바이트 단위로 남은 용량을 왁인&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;df -h&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;: 보기 좋게 보여줌&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span&gt;df .&lt;/span&gt;&lt;/b&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 현재 디렉토리가 포함된 파티션의 남은 용량을 확인&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1598276370152&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;gt; df -h
Filesystem      Size   Used  Avail Capacity iused      ifree %iused  Mounted on
/dev/disk1s1   466Gi   10Gi  379Gi     3%  487648 4881965192    0%   /
devfs          190Ki  190Ki    0Bi   100%     659          0  100%   /dev
/dev/disk1s2   466Gi   73Gi  379Gi    17% 1142296 4881310544    0%   /System/Volumes/Data
/dev/disk1s5   466Gi  2.0Gi  379Gi     1%       2 4882452838    0%   /private/var/vm
map auto_home    0Bi    0Bi    0Bi   100%       0          0  100%   /System/Volumes/Data/home&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-size=&quot;size14&quot; data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;&lt;b&gt;현재 디렉토리의 용량 확인&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;&lt;span&gt;du -a&lt;/span&gt;&lt;/b&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;:&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;현재 디렉토리의 사용량을 파일단위 출력&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span&gt;du -s&lt;/span&gt;&lt;/b&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 총 사용량을 확인&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span&gt;du -h&lt;/span&gt;&lt;/b&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 보기 좋게 바꿔줌&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span&gt;du -sh *&lt;/span&gt;&lt;/b&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 한단계 서브디렉토리 기준으로 보여준다.&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1598276514027&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;gt; du -sh *
4.0K	Dockerfile
4.0K	HELP.md
 12K	README.md
 34M	build
4.0K	build.gradle
 60K	gradle
8.0K	gradlew
4.0K	gradlew.bat
284K	kube-logging
 48K	kube-resource
4.0K	kube-sample.iml
 24K	nginx
4.0K	settings.gradle
4.0K	src&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>인프라/운영체제</category>
      <category>df</category>
      <category>Du</category>
      <category>OS</category>
      <category>Volume</category>
      <category>디스크 사용량</category>
      <category>볼륨</category>
      <category>운영체제</category>
      <author>여성게</author>
      <guid isPermaLink="true">https://coding-start.tistory.com/380</guid>
      <comments>https://coding-start.tistory.com/380#entry380comment</comments>
      <pubDate>Sat, 22 Aug 2020 00:23:19 +0900</pubDate>
    </item>
    <item>
      <title>Elasticsearch - 퍼포먼스 튜닝하는 방법 by ebay</title>
      <link>https://coding-start.tistory.com/379</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AC2qa/btqGTmVNGLc/BV3oixgrWHMxlFOiuqXXFK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AC2qa/btqGTmVNGLc/BV3oixgrWHMxlFOiuqXXFK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AC2qa/btqGTmVNGLc/BV3oixgrWHMxlFOiuqXXFK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAC2qa%2FbtqGTmVNGLc%2FBV3oixgrWHMxlFOiuqXXFK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1597809712130&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[번역] Elasticsearch 퍼포먼스 튜닝 방법 - ebay&quot; data-og-description=&quot;Elasticsearch에 대해 검색하다가 ebay에 퍼포먼스 튜닝방법에 대해 좋은 글이 있어서 간단하게 정리해봤다. 새롭게 알게된 사실이 많아서 좋았다. 정리 잘된 기술 블로그를 보는것은 책을 읽는거보�&quot; data-og-host=&quot;wedul.site&quot; data-og-source-url=&quot;https://wedul.site/613#at_pco=smlwn-1.0&amp;amp;at_si=5f3ca285d077be45&amp;amp;at_ab=per-2&amp;amp;at_pos=0&amp;amp;at_tot=1&quot; data-og-url=&quot;https://wedul.site/613&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cyQ4hy/hyHdnrdqx2/QzeEhRm62Vg5kPgLAZ2fgk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/b61rQo/hyHdsTAM1Y/fKS4URhnplWDrP5vx1wvl0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800&quot;&gt;&lt;a href=&quot;https://wedul.site/613#at_pco=smlwn-1.0&amp;amp;at_si=5f3ca285d077be45&amp;amp;at_ab=per-2&amp;amp;at_pos=0&amp;amp;at_tot=1&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://wedul.site/613#at_pco=smlwn-1.0&amp;amp;at_si=5f3ca285d077be45&amp;amp;at_ab=per-2&amp;amp;at_pos=0&amp;amp;at_tot=1&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cyQ4hy/hyHdnrdqx2/QzeEhRm62Vg5kPgLAZ2fgk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/b61rQo/hyHdsTAM1Y/fKS4URhnplWDrP5vx1wvl0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;[번역] Elasticsearch 퍼포먼스 튜닝 방법 - ebay&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;Elasticsearch에 대해 검색하다가 ebay에 퍼포먼스 튜닝방법에 대해 좋은 글이 있어서 간단하게 정리해봤다. 새롭게 알게된 사실이 많아서 좋았다. 정리 잘된 기술 블로그를 보는것은 책을 읽는거보�&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;wedul.site&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Search-Engine/Elasticsearch&amp;amp;Solr</category>
      <author>여성게</author>
      <guid isPermaLink="true">https://coding-start.tistory.com/379</guid>
      <comments>https://coding-start.tistory.com/379#entry379comment</comments>
      <pubDate>Wed, 19 Aug 2020 13:02:16 +0900</pubDate>
    </item>
    <item>
      <title>Gradle - Could not initialize class org.codehaus.groovy.runtime.InvokerHelper(Spring, gradle project)</title>
      <link>https://coding-start.tistory.com/378</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bPKTFa/btqGNFOoZa1/pKM6pOAAJqkWHKpz5wgkX1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bPKTFa/btqGNFOoZa1/pKM6pOAAJqkWHKpz5wgkX1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bPKTFa/btqGNFOoZa1/pKM6pOAAJqkWHKpz5wgkX1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbPKTFa%2FbtqGNFOoZa1%2FpKM6pOAAJqkWHKpz5wgkX1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그래들 프로젝트를 사용중인데, 간혹 idea에서 &quot;Could not initialize class org.codehaus.groovy.runtime.InvokerHelper&quot;라는 에러 메시지가 뜨는 경우가 있다. 여러가지 요인이 있을 수 있지만, 필자가 저 에러를 보았던 순간은 jdk 1.8에서 jdk14 버전으로 올리면서 났던 에러 메시지 인데, 이유는 gradle version이 jdk14를 지원하지 못하는 낮은 버전이었기 때문이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;스크린샷 2020-08-17 오후 6.05.24.png&quot; data-origin-width=&quot;2382&quot; data-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cqTMXs/btqGG0TVNx3/1K07CM4auvkrU3Y2IP8qFk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cqTMXs/btqGG0TVNx3/1K07CM4auvkrU3Y2IP8qFk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cqTMXs/btqGG0TVNx3/1K07CM4auvkrU3Y2IP8qFk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcqTMXs%2FbtqGG0TVNx3%2F1K07CM4auvkrU3Y2IP8qFk%2Fimg.png&quot; data-filename=&quot;스크린샷 2020-08-17 오후 6.05.24.png&quot; data-origin-width=&quot;2382&quot; data-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그래서, 프로젝트 디렉토리중 &lt;i&gt;&lt;b&gt;&quot;gradle/wrapper/gradle-wrapper.properties&quot;&lt;/b&gt;&lt;/i&gt;에서 gradle 버전을 올려주어서 해결하였다.(5.x -&amp;gt; 6,3)&lt;/p&gt;</description>
      <category>Web/Gradle</category>
      <category>build</category>
      <category>gradle</category>
      <category>gradle wrapper</category>
      <category>gradlew</category>
      <category>JDK</category>
      <category>그래들</category>
      <category>그래들 래퍼</category>
      <author>여성게</author>
      <guid isPermaLink="true">https://coding-start.tistory.com/378</guid>
      <comments>https://coding-start.tistory.com/378#entry378comment</comments>
      <pubDate>Mon, 17 Aug 2020 18:06:06 +0900</pubDate>
    </item>
    <item>
      <title>MongoDB - 백업하고 복구하기(mongodump&amp;amp;mongorestore)</title>
      <link>https://coding-start.tistory.com/377</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c6Y7Fm/btqGFah936s/KPjlpMfaGHOxUNU4yapnDK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c6Y7Fm/btqGFah936s/KPjlpMfaGHOxUNU4yapnDK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c6Y7Fm/btqGFah936s/KPjlpMfaGHOxUNU4yapnDK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc6Y7Fm%2FbtqGFah936s%2FKPjlpMfaGHOxUNU4yapnDK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이번에 다루어볼 내용은 몽고디비에서 데이터를 백업하고 복구하는 방법이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;&lt;b&gt;백업하기(덤프, dump)&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;p&gt;몽고디비가 설치되어 있다면, mongodump라는 명령어로 몽고디비 데이터를 백업할 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1597583772953&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;gt; mongodump --host 127.0.0.1 --port 27017&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위 명령으로 데이터를 백업한다면, 현재 디렉토리에 /dump 디렉토리가 생기고 이 디렉토리 밑에 데이터가 복구되어 있다.(DB 별로 폴더가 생겨있고, 그 폴더안에 BSON으로 데이터가 백업되어 있다.)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1597583829607&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;gt; mongodump --out ~/mongo_backup --host 127.0.0.1 --port 27017&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;--out 옵션으로 데이터 백업의 디렉토리 위치를 정해줄 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1597583854638&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;gt; mongodump --out &amp;lt;dump data path&amp;gt; --host 127.0.0.1 --port 27017 -u &amp;lt;username&amp;gt; -p &amp;lt;password&amp;gt; &lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;username/password로 인증이 필요하다면, 위 명령어로 백업이 가능하다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1597583943796&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;gt; mongodump --out &amp;lt;dump data path&amp;gt; --host 127.0.0.1 --port 27017 -u &amp;lt;username&amp;gt; -p &amp;lt;password&amp;gt; --db &amp;lt;덤프할 db명&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;만약 몽고디비에서 특정 데이터베이스만 백업하고 싶다면, 위처럼 --db 옵션을 이용하면 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1598320852016&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;gt; mongodump --out &amp;lt;dump data path&amp;gt; --host &amp;lt;dbhost&amp;gt; --port 27017 -u &amp;lt;username&amp;gt; -p &amp;lt;password&amp;gt; --db &amp;lt;dbname&amp;gt; --collection &amp;lt;collectionName&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;특정 컬렉션 단위까지 세분화하여 백업하려면 --collection 옵션을 이용한다. 만약 mongodump&amp;amp;mongorestore에서 &quot;error connecting to db server: server returned error on SASL authentication step:Authentication failed&quot; 에러가 났다면, 아래와 같이 옵션하나를 넣어준다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1598401736121&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;--authenticationDatabase admin&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;&lt;b&gt;복구하기(restore)&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;p&gt;다음은 위에서 덤프한 데이터를 복구하는 방법이다. 복구는 mongorestore라는 명령어를 이용한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1597584131954&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;gt; mongorestore --host 127.0.0.1 --port 27017 \
-u &amp;lt;username&amp;gt; -p &amp;lt;password&amp;gt; --drop &amp;lt;drop db name&amp;gt; \
--db &amp;lt;복구할 db name&amp;gt; &amp;lt;복구할 덤프데이터가 있는 디렉토리&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;--drop 옵션은 복구전에 드랍시킬 데이터베이스 명을 입력하면 된다.(복구 전 원래 데이터베이스를 드랍시키고 백업 데이터로 새로 복구하는 것이다.) 위 명령을 간단하게 작성해보면 아래와 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1597584215890&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;gt; mongorestore --host 127.0.0.1 --port 27017 --db local /mongo_backup/local&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;특정 데이터베이스를 복구하고 싶다면, 덤프 데이터가 있는 디렉토리에서 특정 데이터베이스의 디렉토리를 명시해야한다. 만약 모든 데이터를 전부다 백업하고 싶다면 아래 명령어를 입력한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1597584286586&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;gt; mongorestore --host 127.0.0.1 --port 27017 &amp;lt;dump data가 있는 디렉토리&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;컬렉션 단위로 리스토어하기 위해서는 --collection 옵션을 사용하며, collectionName.bson까지 백업데이터 경로를 명시해주어야한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1598320958373&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;gt; mongorestore --host &amp;lt;dbhost&amp;gt; --port 27017 --db &amp;lt;dbname&amp;gt; --collection &amp;lt;collectionName&amp;gt; &amp;lt;data-dump-path/dbname/collection.bson&amp;gt; --drop
ex)
&amp;gt; mongorestore --port 27017 --db test2 --collection rest2 /mydata/restoredata/test/restaurants.bson --drop&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;여기까지 간단하게 몽고디비 데이터 백업 및 복구에 대해 다루어보았다.&lt;/p&gt;</description>
      <category>Database/MongoDB</category>
      <category>DB</category>
      <category>Dump</category>
      <category>Mongo</category>
      <category>mongodump</category>
      <category>mongorestore</category>
      <category>nosql</category>
      <category>덤프</category>
      <category>몽고</category>
      <category>백업</category>
      <category>복구</category>
      <author>여성게</author>
      <guid isPermaLink="true">https://coding-start.tistory.com/377</guid>
      <comments>https://coding-start.tistory.com/377#entry377comment</comments>
      <pubDate>Sun, 16 Aug 2020 22:26:21 +0900</pubDate>
    </item>
    <item>
      <title>Kubernetes - 볼륨(Volume),퍼시스턴트 볼륨&amp;amp;볼륨 클레임(persistent volume&amp;amp;claim)</title>
      <link>https://coding-start.tistory.com/376</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmiMB6/btqGG8Rzxkq/HUmVqfcZ1ytlb6KprK2xUK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmiMB6/btqGG8Rzxkq/HUmVqfcZ1ytlb6KprK2xUK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmiMB6/btqGG8Rzxkq/HUmVqfcZ1ytlb6KprK2xUK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmiMB6%2FbtqGG8Rzxkq%2FHUmVqfcZ1ytlb6KprK2xUK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;오늘은 쿠버네티스의 볼륨에 대해 다루어 볼 것이다. 간단하게 몇가지 볼륨 플러그인에 대해 예제를 다루어보고, 퍼시스턴트 볼륨&amp;amp;볼륨 클레임에 대해 다루어본다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1597499101057&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;object&quot; data-og-title=&quot;yoonyeoseong/kubernetes-sample&quot; data-og-description=&quot;Contribute to yoonyeoseong/kubernetes-sample development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/yoonyeoseong/kubernetes-sample&quot; data-og-url=&quot;https://github.com/yoonyeoseong/kubernetes-sample&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bbNSX9/hyG9zlFqN8/QBhLu357uNXb4KerpulCO1/img.jpg?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400,https://scrap.kakaocdn.net/dn/z8VCP/hyG9CioLxl/wyqHUQqXzntfoEd1VbK7Ek/img.png?width=1211&amp;amp;height=630&amp;amp;face=0_0_1211_630,https://scrap.kakaocdn.net/dn/bFiqAm/hyG9C3K1gE/VnVvKW3mK2zYaNKVfcR9HK/img.png?width=696&amp;amp;height=668&amp;amp;face=0_0_696_668&quot;&gt;&lt;a href=&quot;https://github.com/yoonyeoseong/kubernetes-sample&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/yoonyeoseong/kubernetes-sample&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bbNSX9/hyG9zlFqN8/QBhLu357uNXb4KerpulCO1/img.jpg?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400,https://scrap.kakaocdn.net/dn/z8VCP/hyG9CioLxl/wyqHUQqXzntfoEd1VbK7Ek/img.png?width=1211&amp;amp;height=630&amp;amp;face=0_0_1211_630,https://scrap.kakaocdn.net/dn/bFiqAm/hyG9C3K1gE/VnVvKW3mK2zYaNKVfcR9HK/img.png?width=696&amp;amp;height=668&amp;amp;face=0_0_696_668');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;yoonyeoseong/kubernetes-sample&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;Contribute to yoonyeoseong/kubernetes-sample development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;&lt;b&gt;emptyDir&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;p&gt;해당 플러그인은 pod가 실행되는 호스트의 디스크를 임시로 컨테이너에 볼륨으로 할당해서 사용하는 방법이다. pod가 사라지면 emptyDir에 마운트해서 사용하는 데이터도 모두 사라진다. 하지만, pod이 종료되지 않고 단순히 container만 재시작된 것이라면 데이터는 유지된다. 주로 대용량 데이터 계산의 중간 연산 저장 용도로 사용한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1597497333934&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Pod
metadata:
  name: springboot-web
spec:
  containers:
  - name: springboot-web
    image: 1223yys/springboot-web:0.1.6
    ports:
    - containerPort: 8080
    volumeMounts:
      - mountPath: /emptyDir
        name: emptyDir_vol
  volumes:
    - name: emptyDir_vol
      emptyDir: {}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;&lt;b&gt;hostPath&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;p&gt;pod가 실행된 호스트의 파일이나 디렉터리를 pod에 마운트한다. emptyDir가 임시 디렉터리를 마운트하는 것이라면, hostPath는 호스트에 있는 실제 파일이나 디렉터리를 마운트하는 것이며 pod를 재시작하더라도 데이터가 보존된다. 보통은 /var/lib/docker 같은 도커 시스템용 디렉토리를 컨테이너에 마운트해 시스템 모니터링 등을 진행할 때 사용하기도 한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1597497424178&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Pod
metadata:
  name: springboot-web
spec:
  containers:
  - name: springboot-web
    image: 1223yys/springboot-web:0.1.6
    ports:
    - containerPort: 8080
    volumeMounts:
      - mountPath: /test-volume
        name: hostPath-vol
  volumes:
    - name: hostPath-vol
      hostPath:
        path: /tmp
        type: Directory&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;실제로 볼륨이 잘 마운트 되었는지 확인해보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1597497442641&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;gt; kubectl exec &amp;lt;pod-name&amp;gt; -it sh
&amp;gt; cd /test-volume
&amp;gt; touch test.txt
&amp;gt; exit
&amp;gt; ls /tmp&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;호스트의 /tmp 디렉토리에 test.txt가 생성되었다면, 볼륨이 잘 마운트 된 것이다. hostPath 볼륨에는 아래와 같이 여러가지 타입이 존재한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;null: hostPath 볼륨을 마운트하기 전에 아무것도 확인하지 않는다.&lt;/li&gt;
&lt;li&gt;DirectoryOrCreate: 설정한 경로에 디렉터리가 없으면 퍼미션이 755인 빈 디렉터리를 만든다.&lt;/li&gt;
&lt;li&gt;Directory: 설정한 경로에 디렉터리가 존재해야한다. 호스트에 해당 디렉터리가 없으면 파드는 ContainerCreating 상태로 남고 생성이 안된다.&lt;/li&gt;
&lt;li&gt;FileOrCreate: 설정한 경로에 파일이 없으면 퍼미션이 644인 빈 파일을 만든다.&lt;/li&gt;
&lt;li&gt;File: Directory와 동일&lt;/li&gt;
&lt;li&gt;Socket: 설정한 경로에 유닉스 소켓 파일이 있어야한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;&lt;b&gt;퍼시스턴트 볼륨&amp;amp;볼륨 클레임(Persistent Volume, Persistent Volume Claim)&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;p&gt;쿠버네티스에서 볼륨을 사용하는 구조는 PV와 PVC로 분리되어있다. PV는 볼륨 자체를 뜻하고 클러스터 안에서 자원으로 다룬다. 파드하고는 별개로 관리되고 별도의 생명주기가 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b6m3oa/btqGGEDtPK8/RySdk7LRaMTSNlLhxGS5PK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b6m3oa/btqGGEDtPK8/RySdk7LRaMTSNlLhxGS5PK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b6m3oa/btqGGEDtPK8/RySdk7LRaMTSNlLhxGS5PK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb6m3oa%2FbtqGGEDtPK8%2FRySdk7LRaMTSNlLhxGS5PK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;PVC는 사용자가 PV에 하는 요청이다. 사용하고 싶은 용량은 얼마인지, 읽기/쓰기는 어떤 모드를 사용하고 싶은지 등을 정하여 요청한다. 즉, 쿠버네티스는 이처럼 파드에 볼륨을 직접 할당하는 형태가 아니라, 중간에 PVC를 두어 파드와 파드가 사용할 스토리지를 분리하는 전략인 것이다. 이렇게 분리됨으로써 이점은 다양한 스토리지를 PV로 사용할 수 있는데, 파드는 어떠한 스토리지의 볼륨인지 신경쓸 필요없이 PVC으로 요청만 하면 되기 때문에 의존성이 줄어들게 되고, manifest도 분리됨으로써 설정파일 자체의 복잡함이 사라진다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;&lt;b&gt;PV&amp;amp;PVC 생명주기&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Provisioning: PV를 만드는 단계를 뜻한다.
&lt;ul&gt;
&lt;li&gt;static provisioning: 미리 PV를 만들어 두고 사용자의 요청이 있으면 미리 만들어둔 PV를 할당한다.(보통 스토리지 용량의 제한이 있을때 사용한다.)&lt;/li&gt;
&lt;li&gt;dynamic provisioning: 사용자가 PVC를 거쳐 PV를 요청했을 때, PV를 생성해 제공한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Binding: 바인딩은 프로비저닝으로 만든 PV를 PVC와 연결하는 단계이다. PVC에서 원하는 스토리지의 용량과 접근 방법을 명시해서 요쳥하면 맞는 PV가 할당된다. 이때 PVC에서 원하는 PV가 없다면 요청은 실패하고, PVC에서 원하는 PV가 생길 때까지 대기하다가 PVC에 바인딩된다.(PVC 하나에 여러 PV가 매핑될 수 없다.)&lt;/li&gt;
&lt;li&gt;Using: PVC는 파드에 설정되고 파드는 PVC를 볼륨으로 인식해서 사용한다. 할당된 PVC는 파드를 유지하는 동안 계속 사용하며 시스템에서 임의로 삭제할 수 없다. 이 기능을 &quot;Storage Object In Use Protection&quot;이라 한다.&lt;/li&gt;
&lt;li&gt;Reclaiming: 사용이 끝난 PVC는 삭제되고 PVC를 사용하던 PV를 초기화하는 과정을 뜻한다. 초기화 정책으로는 아래와 같다.
&lt;ul&gt;
&lt;li&gt;Retain: PV를 그대로 보존한다. PVC가 삭제되면 사용 중이던 PV는 해제(released)상태라서 아직 다른 PVC가 재사용할 수 없다.(데이터는 아직 그대로 보존되어있다.) 만약 해당 PV를 재사용하려면 아래와 같은 순서로 직접 초기화해줘야한다.
&lt;ol&gt;
&lt;li&gt;PV삭제. 만약 PV가 외부 스토리지와 연결되어있다면 PV는 삭제되더라도 외부 스토리지의 볼륨은 그대로 남아있다.&lt;/li&gt;
&lt;li&gt;외부 스토리지에 남은 데이터를 직접 정리한다.&lt;/li&gt;
&lt;li&gt;남은 스토리지의 볼륨을 삭제하거나 재사용하려면 해당 볼륨을 이용하는 PV를 다시 만든다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;Delete: PV를 삭제하고 연결된 외부 스토리지 쪽의 볼륨도 삭제한다. 동적 프로비저닝은 기본적으로 해당 정책을 따른다.&lt;/li&gt;
&lt;li&gt;Recycle: PV의 데이터들을 삭제하고 다시 새로운 PVC에서 PV를 사용할 수 있도록한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이제 실제로 실습을 통해 알아보자. 다음은 퍼시스턴트 볼륨 템플릿이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1597497791210&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apiVersion: v1
kind: PersistentVolume
metadata:
  name: persistent-volume
  namespace: levi-volume
spec:
  capacity:
    storage: 1Gi
  volumeMode: Filesystem
  accessModes:
    - ReadWriteOnce
  storageClassName: manual
  persistentVolumeReclaimPolicy: Delete
  hostPath:
    path: /tmp&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;간단하게 설정파일에 작성된 설정을 설명하면 아래와 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;accessModes
&lt;ul&gt;
&lt;li&gt;ReadWriteOne: 노드 하나에만 볼륨을 읽기/쓰기하도록 마운트한다.&lt;/li&gt;
&lt;li&gt;ReadOnlyMany: 여러 개 노드의 읽기 전용으로 마운트한다.&lt;/li&gt;
&lt;li&gt;ReadWriteMany: 여러 개 노드에서 읽기/쓰기를 허용하도록 마운트한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;storageClassName: 스토리지 클래스를 설정하는 필드이고, PVC가 특정 스토리지 클래스를 명시하여 요청하면 해당 스토리지 클래스로 선언된 PV와 연결된다. 만약 스토리지 클래스를 설정하지 않았다면, 특정 스토리지 클래스를 명시하지 않은 PVC가 요청하면 매핑된다.&lt;/li&gt;
&lt;li&gt;persistentVolumeReclaimPolicy: PV가 해제되었을 때의 초기화 옵션을 설정한다.(Retain/Recycle/Delete)&lt;/li&gt;
&lt;li&gt;.spec.hostPath: 해당 PV의 볼륨 플러그인을 명시한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;볼륨이 잘 생성되었는지 확인해보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1597497813394&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;gt; kubectl apply -f ./kube-resource/persistent-volume-sample.yaml
&amp;gt; kubectl get pvc -n levi-volume
NAME                CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   REASON   AGE
persistent-volume   1Gi        RWO            Delete           Available           manual       &lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;STATUS는 아래와 같이 4가지의 상태값을 갖는다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Available: PVC에서 사용할 수 있는 상태&lt;/li&gt;
&lt;li&gt;Bound: 특정 PVC에 연결된 상태&lt;/li&gt;
&lt;li&gt;Released: PVC는 삭제되었고, PV는 아직 초기화되지 않은 상태&lt;/li&gt;
&lt;li&gt;Failed: 자동 초기화를 실패한 상태&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;다음은 퍼시스턴트 볼륨 클레임 설정이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1597497917106&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: persistent-volume-claim
  namespace: levi-volume-claim
spec:
  volumeMode: Filesystem
  accessModes:
    - ReadWriteOnce
  storageClassName: manual
  resources:
    requests:
      storage: 500Mi&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;다른 내용은 볼륨의 설정과 크게 차이가 없고 한가지만 설명하자면, &quot;&lt;span&gt;.spec.resources.requests.storage&lt;/span&gt;&quot;는 &lt;span&gt;자원을 얼마나 사용할 것인지 명시하는 것이며, PV의 용량보다 높다면, 할당되지 않고 Pending 상태가 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;볼륨 클레임이 잘 생성되었는지 확인해보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1597498047126&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;gt; kubectl apply -f ./kube-resource/persistent-volume-claim-sample.yaml
&amp;gt; kubectl get pvc -n levi-volume-claim
NAME                      STATUS   VOLUME              CAPACITY   ACCESS MODES   STORAGECLASS   AGE
persistent-volume-claim   Bound    persistent-volume   1Gi        RWO            manual         11s
&amp;gt; kubectl get pv -n levi-volume
NAME                CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                                       STORAGECLASS   REASON   AGE
persistent-volume   1Gi        RWO            Delete           Bound    levi-volume-claim/persistent-volume-claim   manual                  4h44m&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;PVC와 PV가 binding 된 이후에는 각각 STATUS가 Bound 상태로 변경되었다. 보통 PV와 PVC를 연동할때는 storageClassName을 보고 연결되는데, 또 다른 방법으로는 label로 연결할 수도 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1597498131928&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#볼륨
apiVersion: v1
kind: PersistentVolume
metadata:
  name: persistent-volume
  namespace: levi-volume
  labels:
    location: local
spec:
  capacity:
    storage: 1Gi
  volumeMode: Filesystem
  accessModes:
    - ReadWriteOnce
  storageClassName: manual
  persistentVolumeReclaimPolicy: Delete
  hostPath:
    path: /tmp

#볼륨 클레임
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: persistent-volume-claim
  namespace: levi-volume-claim
spec:
  volumeMode: Filesystem
  accessModes:
    - ReadWriteOnce
  storageClassName: manual
  resources:
    requests:
      storage: 500Mi
  selector:
    matchLabels:
      location: local&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;마지막으로 pod에 볼륨을 마운트해보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1597498151413&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apiVersion: apps/v1
kind: Deployment
metadata:
  name: sample-deployment
spec:
  replicas: 2
  selector:
    matchLabels:
      app: springboot-web
  template:
    metadata:
      labels:
        app: springboot-web
    spec:
      containers:
        - name: springboot-web
          image: 1223yys/springboot-web:0.2.5
          imagePullPolicy: Always
          ports:
            - containerPort: 8080
          volumeMounts:
            - mountPath: /test-volume
              name: persistent-volume
          livenessProbe:
            httpGet:
              port: 9090
              path: /api
            initialDelaySeconds: 60
          readinessProbe:
            httpGet:
              port: 9090
              path: /api
            initialDelaySeconds: 60
      volumes:
        - name: persistent-volume
          persistentVolumeClaim:
            claimName: persistent-volume-claim&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;만약 예제대로 따라왔다면, pod은 뜨지 못하고 pending된 상태로 머물러 있을 것이다. 왜냐하면 클러스터는 클레임을 사용하는 pod와 동일한 네임스페이스에 있어야하기 때문이다. 위 deployment는 네임스페이스가 default이므로, 볼륨 클레임을 default 네임스페이스에 하나 생성해주어야 한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;여기까지 정말 간단하게 쿠버네티스 볼륨과 볼륨 클레임에 대해 다루어보았다. 사실 다루어볼 볼륨 플러그인이 아주 많기 때문에 다음 포스팅에서 더 자세히 다루어볼 것이다.&lt;/p&gt;</description>
      <category>인프라/Docker&amp;amp;Kubernetes</category>
      <category>emptyDir</category>
      <category>hostPath</category>
      <category>Kubernetes</category>
      <category>mount</category>
      <category>Volume</category>
      <category>volume claim</category>
      <category>마운트</category>
      <category>볼륨</category>
      <category>볼륨 클레임</category>
      <category>쿠버네티스</category>
      <author>여성게</author>
      <guid isPermaLink="true">https://coding-start.tistory.com/376</guid>
      <comments>https://coding-start.tistory.com/376#entry376comment</comments>
      <pubDate>Sat, 15 Aug 2020 22:31:54 +0900</pubDate>
    </item>
    <item>
      <title>Kubernetes - ingress nginx 설치 및 사용법</title>
      <link>https://coding-start.tistory.com/375</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cvfj04/btqGftf98yy/4Rrk2mmH2w9AJlEJHQ7gy0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cvfj04/btqGftf98yy/4Rrk2mmH2w9AJlEJHQ7gy0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cvfj04/btqGftf98yy/4Rrk2mmH2w9AJlEJHQ7gy0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcvfj04%2FbtqGftf98yy%2F4Rrk2mmH2w9AJlEJHQ7gy0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이번 포스팅에서 다루어볼 내용은 간단하게 쿠버네티스 ingress-nginx를 설치하고, 외부 트래픽을 내부 팟에게 전달해주는 예제이다. 바로 예제로 들어간다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1596371348667&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;gt; git clone https://github.com/kubernetes/ingress-nginx.git
&amp;gt; cd ./ingress-nginx/deploy/static/provider/baremetal
&amp;gt; kubectl apply -f .
&amp;gt; kubectl get deploy -n ingress-nginx
NAME                       READY   UP-TO-DATE   AVAILABLE   AGE
ingress-nginx-controller   1/1     1            1           60s&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;여기까지 따라왔다면 설치는 완료되었고, ingress-nginx를 위한 서비스 등이 떴을 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1596371411190&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;kubectl get svc -n ingress-nginx
NAME                                 TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)                      AGE
ingress-nginx-controller             NodePort    10.97.27.106   &amp;lt;none&amp;gt;        80:30431/TCP,443:31327/TCP   4m35s&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;30431로 접속해보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1596371460633&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;gt; curl levi.local.com:30431
&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;&amp;lt;title&amp;gt;404 Not Found&amp;lt;/title&amp;gt;&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
&amp;lt;center&amp;gt;&amp;lt;h1&amp;gt;404 Not Found&amp;lt;/h1&amp;gt;&amp;lt;/center&amp;gt;
&amp;lt;hr&amp;gt;&amp;lt;center&amp;gt;nginx/1.19.1&amp;lt;/center&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;호스트 설정을 통해 levi.local.com을 localhost로 포워딩하도록 설정하였다. 실습에 localhost는 사용하기 힘들기 때문에 etc/hosts 설정을 통해 로컬을 특정 도메인처럼 할당해보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1596371558193&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;gt; sudo vi /etc/hosts
127.0.0.1 levi.local.com&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이제 ingress-nginx가 포워딩할 웹어플리케이션 팟을 띄워보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1596372763784&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apiVersion: apps/v1
kind: Deployment
metadata:
  name: sample-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: springboot-web
  template:
    metadata:
      labels:
        app: springboot-web
    spec:
      containers:
        - name: springboot-web
          image: 1223yys/springboot-web:0.2.5
          imagePullPolicy: Always
          ports:
            - containerPort: 8080
          livenessProbe:
            httpGet:
              port: 8080
              path: /api
            initialDelaySeconds: 60
          readinessProbe:
            httpGet:
              port: 8080
              path: /api
            initialDelaySeconds: 60&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;디플로이먼트 컨트롤러로 springboot-web 애플리케이션 팟을 관리하도록 매니페스트를 작성하였다. 해당 매니패스트를 적용해보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1596372806355&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;gt; kubectl apply -f deployment.yaml&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;다음으로는 springboot-web으로 접근할 수 있게 해주는 외부 통로인 서비스를 작성해보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1596372841218&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Service
metadata:
  name: springboot-web-service
spec:
  selector:
    app: springboot-web
  ports:
    - name: http
      port: 80
      targetPort: 8080&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;해당 서비스도 배포해보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1596372900370&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;gt; kubectl apply -f service.yaml&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;마지막으로 ingress nginx 매니페스트 파일을 작성해보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1596372924602&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: nginx-ingress-sample
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
  - host: levi.local.com
    http:
      paths:
      - path: /
        backend:
          serviceName: springboot-web-service
          servicePort: 80&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위 매니페스트 파일에서 host를 유의하자. 포스팅 초반에 localhost를 로컬 도메인으로 할당해야한다 했는데, 그 이유가 위 host 때문이다. 위에 보이는 host는 실제로 http 요청이 들어올때 요청 헤어의 &lt;u&gt;&lt;b&gt;&quot;Host : levi.local.com&quot;&lt;/b&gt;&lt;/u&gt; 을 참조하기 때문이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이제 요청을 보내보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1596373032883&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;gt; curl http://levi.local.com:30431/api
new api !&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;br /&gt;&amp;nbsp;ingress-nginx를 통해 웹앱 팟에 잘 접근되는 것을 확인할 수 있다.&lt;/p&gt;</description>
      <category>인프라/Docker&amp;amp;Kubernetes</category>
      <category>ingress</category>
      <category>Kubernetes</category>
      <category>nginx</category>
      <category>디플로이먼트</category>
      <category>서비스</category>
      <category>엔진엑스</category>
      <category>인그레스</category>
      <category>컨테이너</category>
      <category>쿠버네티스</category>
      <category>팟</category>
      <author>여성게</author>
      <guid isPermaLink="true">https://coding-start.tistory.com/375</guid>
      <comments>https://coding-start.tistory.com/375#entry375comment</comments>
      <pubDate>Sun, 2 Aug 2020 22:52:06 +0900</pubDate>
    </item>
    <item>
      <title>Git - 체리픽(Cherry-pick), 다른 브랜치 혹은 다른 레포지토리의 커밋 가져오기</title>
      <link>https://coding-start.tistory.com/374</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/coIEhC/btqGc8YKtGE/GG7tk8adhuucy2fpvpikRK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/coIEhC/btqGc8YKtGE/GG7tk8adhuucy2fpvpikRK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/coIEhC/btqGc8YKtGE/GG7tk8adhuucy2fpvpikRK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcoIEhC%2FbtqGc8YKtGE%2FGG7tk8adhuucy2fpvpikRK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;오늘 다루어볼 내용은 git의 cherry-pick(체리픽)이다. 보통 체리픽을 사용하는 이유는 다른 브랜치의 커밋의 일부만 가져올때 많이 사용한다. 간단하게 예제를 다루어보자.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1596366718672&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;gt; gb
* [1] feature/add-function
  [2] feature/update-function&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위처럼 두개의 브랜치가 존재하고 각 브랜치의 커밋로그는 아래와 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1596366801649&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;gt; feature/add-function
* 13e321a - (HEAD -&amp;gt; feature/add-function) add c.txt (9 minutes ago) &amp;lt;levi.yoon&amp;gt;
* f894b25 - add b.txt (9 minutes ago) &amp;lt;levi.yoon&amp;gt;
* 35402a1 - add a.txt (9 minutes ago) &amp;lt;levi.yoon&amp;gt;

&amp;gt; feature/update-function
* cb6a172 - (HEAD -&amp;gt; feature/update-function) add d.txt (7 minutes ago) &amp;lt;levi.yoon&amp;gt;
* d516ac8 - update a.txt (7 minutes ago) &amp;lt;levi.yoon&amp;gt;
* 13e321a - (feature/add-function) add c.txt (9 minutes ago) &amp;lt;levi.yoon&amp;gt;
* f894b25 - add b.txt (10 minutes ago) &amp;lt;levi.yoon&amp;gt;
* 35402a1 - add a.txt (10 minutes ago) &amp;lt;levi.yoon&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;여기서 feature/update-function 브랜치에 있는 &quot;d516ac8 - update a.txt&quot; 이 커밋내용만 가져와 feature/add-function 브랜치에 넣고 싶을 때 cherry-pick을 이용한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1596366932970&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;gt; git checkout feature/add-function
&amp;gt; git cherry-pick d516ac8
&amp;gt; git log
* 09edc77 - (HEAD -&amp;gt; feature/add-function) update a.txt (46 seconds ago) &amp;lt;levi.yoon&amp;gt;
* 13e321a - add c.txt (12 minutes ago) &amp;lt;levi.yoon&amp;gt;
* f894b25 - add b.txt (12 minutes ago) &amp;lt;levi.yoon&amp;gt;
* 35402a1 - add a.txt (12 minutes ago) &amp;lt;levi.yoon&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;지금 한 예제는 같은 레포지토리의 다른 브랜치에서 커밋을 가져왔지만, 다른 원격 레포지토리의 특정 브랜치에 있는 커밋내용도 내 레포지토리 브랜치에 넣을 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Tools/Git&amp;amp;GitHub</category>
      <category>cherry-pick</category>
      <category>git</category>
      <category>github</category>
      <category>Merge</category>
      <category>Rebase</category>
      <category>깃</category>
      <category>깃허브</category>
      <category>리베이스</category>
      <category>체리픽</category>
      <category>커밋</category>
      <author>여성게</author>
      <guid isPermaLink="true">https://coding-start.tistory.com/374</guid>
      <comments>https://coding-start.tistory.com/374#entry374comment</comments>
      <pubDate>Sun, 2 Aug 2020 20:17:08 +0900</pubDate>
    </item>
    <item>
      <title>Java - 자바 날짜&amp;amp;시간 java.time 패키지(LocalDateTime, ZoneDateTime)</title>
      <link>https://coding-start.tistory.com/373</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/l1g5d/btqF5dmoLf7/DQidmRrkPceVHj3xKPkpBK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/l1g5d/btqF5dmoLf7/DQidmRrkPceVHj3xKPkpBK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/l1g5d/btqF5dmoLf7/DQidmRrkPceVHj3xKPkpBK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fl1g5d%2FbtqF5dmoLf7%2FDQidmRrkPceVHj3xKPkpBK%2Fimg.jpg&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;오늘 다루어볼 내용은 jdk1.8의 날짜&amp;amp;시간을 다루는 java.time 패키지를 다루어볼 것이다. 바로 java.time 패키지 내용을 다루기 전에 우선 프로그래밍에서의 날짜와 시간에 대해 표준인 ISO-8601에 대해 먼저 알아본다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;&lt;b&gt;ISO-8601&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;i&gt;&lt;b&gt;&amp;lt;wiki&amp;gt;&lt;/b&gt;&lt;/i&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;ISO 8601&amp;nbsp;Data elements and interchange formats - Information interchange - Representation of dates and times은&amp;nbsp;날짜와&amp;nbsp;시간과 관련된 데이터 교환을 다루는&amp;nbsp;국제 표준이다. 이 표준은&amp;nbsp;국제 표준화 기구(ISO)에 의해 공포되었으며 1988년에 처음으로 공개되었다. 이 표준의 목적은 날짜와 시간을 표현함에 있어 명백하고 잘 정의된 방법을 제공함으로써, 날짜와 시간의 숫자 표현에 대한 오해를 줄이고자함에 있는데, 숫자로 된 날짜와 시간 작성에 있어&amp;nbsp;다른 관례를 가진 나라들간의 데이터가 오갈때 특히 그렇다.&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;일반적으로, ISO 8601는&amp;nbsp;그레고리력&amp;nbsp;(proleptic Gregorian도 가능)에서의 날짜와 (부가적으로&amp;nbsp;시간대&amp;nbsp;정보를 포함하는)&amp;nbsp;24시간제에 기반하는 시간,&amp;nbsp;시간 간격(time interval)&amp;nbsp;그리고 그들의 조합에 대한 표현과 형식에 적용된다. 이 표준은 표현할 날짜/시간 요소에 어떠한 특정 의미도 할당하지 않는다; 그 의미는 사용 맥락에 따라 달라질 것이다. 추가로, 표현될 날짜와 시간은 표준 내에서의 지정된 의미의 숫자(예를 들자면,&amp;nbsp;중국 달력의 년도&amp;nbsp;이름)가 아니고서는 단어를 포함할 수 없으며 단어들은&amp;nbsp;문자(예: 이미지, 소리)를 사용하지 않는다.&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;교환을 위한 표현에서, 날짜와 시간은 재배치되어서, 가장 큰 시간 용어(년도)가 왼쪽에 놓이며 각각의 더 작은 용어들은 이전 용어의 우측에 놓이게 된다. 표현은 아라비아 숫자와 표준 내에서 특정 의미를 제공하는 (&quot;-&quot;, &quot;:&quot;, &quot;T&quot;, &quot;W&quot; 그리고 &quot;Z&quot;와 같은) 어떤 문자들로 작성되어야 한다. 그것이 의미하는 바는, &quot;January&quot; 혹은 &quot;Thursday&quot;처럼 날짜의 일부를 작성하는 어떤 평범한 방법이 교환 표현에서는 허용되지 않는다는 것이다.&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;참조 : &lt;a style=&quot;color: #000000;&quot; href=&quot;https://ko.wikipedia.org/wiki/ISO_8601&quot;&gt;https://ko.wikipedia.org/wiki/ISO_8601&lt;/a&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위 내용은 위키 내용을 인용하였는데, 한마디로 세계에서 각기 다른 시간대에서 사용하기 위한 날짜&amp;amp;시간에 대한 표준을 정해놓은 것이다. 포맷으로는 보통 아래와 같은 포맷을 다룬다.(물론 기본형식이 있지만 아래 확장 형식을 대부분 사용하는 듯하다.)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1596035342356&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;- 날짜(년월일) : YYYY-MM-DD
- 날짜(년월) : YYYY-MM
- 날짜&amp;amp;시간 : YYYY-MM-DDThh:mm:ss(YYYY-MM-DDThh:mm:ss.sss)
- 시간 : hh:mm:ss(hh:mm:ss.sss)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;날짜와 시간을 다루는데 아주 중요한 것중 하나는 &quot;표준 시간대 지정자&quot;이다. ISO-8601의 표준 시간대는 (불특정 위치의) &lt;i&gt;&lt;b&gt;&quot;지역 시간&quot;&lt;/b&gt;&lt;/i&gt;(local time), &lt;i&gt;&lt;b&gt;&quot;&lt;/b&gt;&lt;b&gt;UTC&quot;&lt;/b&gt;&lt;/i&gt; 혹은 &lt;i&gt;&lt;b&gt;&quot;&lt;/b&gt;&lt;b&gt;UTC&lt;/b&gt;&lt;b&gt;의 오프셋&quot;&lt;/b&gt;&lt;/i&gt;으로 표현된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;만약 UTC 관계 정보에 시간 표현이 함께 주어지지 않는다면, 시간은 지역 시간으로 간주된다. 동일한 시간대에서 통신 시 지역 시간을 가정하는 것이 가장 안전할지 몰라도, 시간대가 다른(국가와 국가간 시차) 지역간의 통신에서 지역시간을 사용하는 경우는 아주 모호하게 된다.&lt;/p&gt;
&lt;p&gt;(UTC 오프셋이 표현되지 않는다면 해당 시간이 어느나라 기준의 시간인지 알수 없기에 각자 나라의 시간대로 표현이 불가능하다.)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;&lt;b&gt;UTC&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;p&gt;시간이 UTC인 경우, 시간 뒤에 빈칸없이 &lt;i&gt;&lt;b&gt;&quot;Z&quot; &lt;/b&gt;&lt;/i&gt;를 직접 추가해야 한다. Z는 오프셋이 0인 UTC를 위한 지역 지정자이다. 그러므로 &quot;09:30:12&quot;의 UTC는 &quot;09:30:12Z&quot;로 표현된다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;&lt;b&gt;UTC에서의 시간 오프셋&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;p&gt;UTC에서의 오프셋은 위에서 Z를 붙였던 것과 동일한 방법으로 시간뒤에 덧붙인다. 우리나라는 기준시보다 9시간이 빠른 나라이기 때문에 시간을 UTC 오프셋으로 표현하게 되면 &quot;09:30:12+09:00&quot;으로 표현하게 된다. 이렇게 UTC 오프셋으로 시간을 표현하게 되면 기준시에 오프셋이 붙어있는 것이기 때문에 해당 시간으로 다른 시간대의 나라의 시간으로도 표현이 명확히 가능하게 된다.(느린 시간대라면 -로 오프셋을 표현한다.)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;&lt;b&gt;java.time 패키지의 핵심 클래스&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;p&gt;날짜와 시간을 하나로 표현하는 Calendar클래스와 달리, java.time 패키지에서는 날짜와 시간을 별도의 클래스로 분리해 놓았다. 시간을 표현할 때는 LocalTime 클래스를 사용하고, 날짜를 표현할 때는 LocalDate클래스를 사용한다. 그리고 날짜와 시간이 모두 필요할 때는 LocalDateTime클래스를 사용하면 된다. 만약 여기에 Time-Zone까지 다뤄야 한다면, ZonedDateTime클래스를 사용한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;LocalDateTime은 기본적으로 Time-zone이 없는 형태이다. 그 말은 ZoneOffset을 표기 하지 않는 날짜 형식을 출력해준다. 물론 ZoneOffset을 고려해서 LocalDateTime 오브젝트를 만들수 있다. 하지만 Time-zone이 없는 개념이기 때문에, 아래와 같이 출력이 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1596037249237&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;LocalDateTime.now() - 2020-07-30T00:38:55.215245
ZonedDateTime.now() - 2020-07-30T00:38:55.215245+09:00[Asia/Seoul]&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Jdk1.8 이전의 날짜&amp;amp;시간을 다루는 Calendar는 ZonedDateTime처럼, 날짜와 시간 그리고 시간대까지 모두 가지고 있다. Date와 유사한 클래스로는 Instant가 있는데, 이 클래스는 날짜와 시간을 초 단위(엄밀히는 나노초까지)로 표현한다. 날짜와 시간을 초단위로 표현한 값을 time stamp라고 부르는데, 이 값은 날짜와 시간을 하나의 정수로 표현할 수 있으므로 날짜와 시간의 차이를 계산하거나 순서를 비교하는데 유리해서 데이터 베이스에서 많이 사용한다.(타임스탬프는 타임존에 대한 정보없이 절대적인 시간을 다루기 때문에 아주 명확한 값을 가지게 된다.) 이외에도 날짜를 더 세부적으로 다룰 수 있는 Year, YearMonth, MonthDay와 같은 클래스도 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1596038984676&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;예제 코드는 millisecond는 조금 다르지만 같은 시간이라 가정하자.

LocalDateTime.now() 
-&amp;gt; 2020-07-30T01:04:38.488822

LocalDateTime.ofInstant(Instant.ofEpochMilli(System.currentTimeMillis()), ZoneId.systemDefault())
-&amp;gt; 2020-07-30T01:04:38.490

LocalDateTime.ofInstant(Instant.ofEpochMilli(System.currentTimeMillis()), ZoneId.of(&quot;Asia/Seoul&quot;))
-&amp;gt; 2020-07-30T01:04:38.490

LocalDateTime.ofInstant(Instant.ofEpochSecond(System.currentTimeMillis() / 1000), ZoneId.systemDefault())
-&amp;gt; 2020-07-30T01:04:38

LocalDateTime.ofInstant(Instant.ofEpochSecond(System.currentTimeMillis() / 1000), ZoneId.of(&quot;Asia/Seoul&quot;))
-&amp;gt; 2020-07-30T01:04:38

LocalDateTime.ofInstant(Instant.ofEpochSecond(System.currentTimeMillis() / 1000), ZoneId.systemDefault()).plusDays(7)
-&amp;gt; 2020-08-06T01:04:38

LocalDateTime.ofEpochSecond(System.currentTimeMillis() / 1000, 0, ZoneOffset.UTC)
-&amp;gt; 2020-07-29T16:04:38

LocalDateTime.ofEpochSecond(System.currentTimeMillis() / 1000, 0, ZoneOffset.ofHours(9))
-&amp;gt; 2020-07-30T01:04:38

ZonedDateTime.now()
-&amp;gt; 2020-07-30T01:04:38.491441+09:00[Asia/Seoul]

LocalDateTime.now().atZone(ZoneId.of(&quot;Asia/Seoul&quot;))
-&amp;gt; 2020-07-30T01:04:38.491568+09:00[Asia/Seoul]&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;작성중...&lt;/p&gt;</description>
      <category>프로그래밍언어/Java&amp;amp;Servlet</category>
      <category>ISO-8601</category>
      <category>LocalDateTime</category>
      <category>UTC</category>
      <category>ZonedDateTime</category>
      <category>ZoneOffset</category>
      <category>날짜시간</category>
      <category>자바</category>
      <author>여성게</author>
      <guid isPermaLink="true">https://coding-start.tistory.com/373</guid>
      <comments>https://coding-start.tistory.com/373#entry373comment</comments>
      <pubDate>Thu, 30 Jul 2020 01:05:46 +0900</pubDate>
    </item>
    <item>
      <title>Java - Reactor switchIfEmpty 사용시 주의점(Lambda, 람다 Lazy Evaluation)</title>
      <link>https://coding-start.tistory.com/372</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rVTHZ/btqF8ZftKxZ/wgtl3Gs19kpD6LaRXcOWkK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rVTHZ/btqF8ZftKxZ/wgtl3Gs19kpD6LaRXcOWkK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rVTHZ/btqF8ZftKxZ/wgtl3Gs19kpD6LaRXcOWkK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrVTHZ%2FbtqF8ZftKxZ%2Fwgtl3Gs19kpD6LaRXcOWkK%2Fimg.jpg&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;리액터의 switchIfEmpty라는 메서드를 다루기 전에 자바의 Lazy evaluation(지연 평가)에 대해 다루어보자. 자바는 논리 operation을 평가할때 lazy evaluation을 사용한다. 예제 코드를 예로 들면 아래와 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1596352079448&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Test
void lazyEvaluationTest() {
    boolean isLazy = lazyEvaluation();
    if (true || isLazy) {
        System.out.println(&quot;method execute!!!&quot;);
    }
}
private boolean lazyEvaluation() {
    System.out.println(&quot;lazy evaluation&quot;);
    return true;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위 테스트에서 결과는 어떻게 될 것인가?&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1596352105970&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;lazy evaluation
method execute!!!
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위와 같이 사용하지 않는 boolean 변수이지만, 미리 isLazy를 만들기 위해 메서드를 호출한다. 여기서 사용하지 않는 변수라는 뜻은 자바에서는 논리 operation에서 이미 참 혹은 거짓이라고 판단이 난 논리식이면 나머지 값 자체를 참조하지 않는다. 이 예는 위와 같이 true || isLazy일때, 이미 앞의 true에서 참이 되었기에 뒤의 isLazy 자체를 참조하지 않는다. 이것을 테스트 하기 위해 아래와 같이 코드를 만들어보았다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1596352293157&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Test
void lazyEvaluationTest() {
    if (true || lazyEvaluation()) {
        System.out.println(&quot;method execute!!!&quot;);
    }
}

private boolean lazyEvaluation() {
    System.out.println(&quot;lazy evaluation&quot;);
    return true;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위 예제코드에서는 lazyEvaluation()를 호출자체를 하지 않는다. 그렇다면 아래와 같은 코드는 결과가 어떻게 될까?&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1596352447416&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Test
void lazyEvaluationTest() {
    System.out.println(&quot;before boolean operation&quot;);
    if (true &amp;amp;&amp;amp; lazyEvaluation()) {
        System.out.println(&quot;method execute!!!&quot;);
    }
}

private boolean lazyEvaluation() {
    System.out.println(&quot;lazy evaluation&quot;);
    return true;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;결과는 아래와 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1596352462952&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;before boolean operation
lazy evaluation
method execute!!!&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;실제 lazyEvaluation()가 필요한 시점에 호출하게 되는 것이다. 여기까지 자바에서 논리 operation은 lazy evaluation 한다는 것은 알았고, 다른 상황에서는 어떻게 될까?&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;자바는 어떠한 지역변수에 값을 할당할때, 그리고 어떠한 메서드의 매개변수를 만들때는 eager evaluation 전략을 따른다. 그 말은 무엇이냐면 어떠한 메서드를 처리하기 전에 그 메서드의 매개변수의 값이 미리 준비가 되어 있어야한다는 것이다. 아래 예제코드를 한번 살펴보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1596353166327&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Test
void eagerEvaluationTest() {
    System.out.println(convertBooleanToString(eagerEvaluation(true)));
}

private String convertBooleanToString(final boolean bool) {
    System.out.println(&quot;convertBooleanToString&quot;);
    return bool ? &quot;true&quot; : &quot;false&quot;;
}

private boolean eagerEvaluation(final boolean bool) {
    System.out.println(&quot;eager evaluation!!!!&quot;);
    return bool;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위 메서드의 결과는 어떻게 될까?&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1596353195262&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;eager evaluation!!!!
convertBooleanToString
true&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;convertBooleanToString을 실행시키기전에 매개변수의 값을 미리 만들기 위해서 eagerEvaluation 메서드를 호출해 값을 만든다. 그렇다면 lazy evaluation하게 하려면 어떻게 하면 될까?&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1596353249374&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Test
void lazyEvaluationTest2() {
    System.out.println(convertBooleanToString(() -&amp;gt; lazyEval(true)));
}

private String convertBooleanToString(final Supplier&amp;lt;Boolean&amp;gt; f) {
    System.out.println(&quot;convertBooleanToString&quot;);
    return f.get() ? &quot;true&quot; : &quot;false&quot;;
}

private boolean lazyEval(final boolean bool) {
    System.out.println(&quot;lazy evaluation&quot;);
    return bool;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위와 같이 매개변수로 람다를 전달하면 된다. 람다를 전달하게 되면 해당 람다가 사용되는 시점에 메서드를 호출하기 때문에 lazy하게 프로그래밍하게 할 수 있다. 이것은 꼭 실행하지 않아도될 로직을 처리하지 않게 할 수 있고, 혹은 실행 시점을 뒤로 미룰 수도 있기 때문에 알고 있으면 아주 좋은 개념이 될것이다. 위 예제를 결과는 아래와 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1596353321249&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;convertBooleanToString
lazy evaluation
true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;실행 순서가 바뀐 것을 볼 수 있다. 그리고 매서드의 매개변수로 담기는 메서드의 lazy 로딩하는 것 외에 지역변수의 초기화를 지연 시킬 수 도 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1596353371245&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Test
void lazyEvaluationTest3() {
    Supplier&amp;lt;Boolean&amp;gt; supplier = () -&amp;gt; lazyEval(true);
    System.out.println(&quot;before method&quot;);
    if (supplier.get()) {
        System.out.println(&quot;method !!&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;여기까지 자바의 eager, lazy evaluation을 다루어보았고, 이제 본론으로 switchIfEmpty를 사용할때 주의해야할 점을 알아보자 !&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1596353494326&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Test
void switchIfEmptyTest() {
    Mono.just(&quot;str&quot;)
            .map(s -&amp;gt; {
                System.out.println(s);
                return s;
            })
            .switchIfEmpty(defaultStr())
            .subscribe();
}

private Mono&amp;lt;String&amp;gt; defaultStr() {
    System.out.println(&quot;defalutStr&quot;);
    return Mono.just(&quot;default&quot;);
}

&amp;lt;console&amp;gt;
defalutStr
str&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위 코드를 보면 우리는 보통 Mono.just가 Mono.empty를 리턴하면 switchIfEmpty를 실행하겠지? 라는 생각을 하기 쉽다. 하지만, 실제 동작은 그렇지 않다. 자바에서는 보통 메서드의 매개변수의 값을 미리 결정시켜놓으려한다.(eager evaluation) 그래서 실제 map을 실행하기 전에 switchIfEmpty를 먼저 실행시켜 값을 만들어 놓는다. 만약 switchIfEmpty안에 실행되는 메서드가 비용이 크고, 실제로 Mono.just는 empty를 반환하지 않는다면, 굳이 실행되지 않아도 되는 비싼 비용의 메서드를 호출해야한다. 이럴때는 우리는 lazy evaluation 전략을 사용하여 switchIfEmpty안의 실행을 실제 필요시점으로 미룰 수 있게 하는 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1596353802895&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Test
void switchIfEmptyTest2() {
    Mono.just(&quot;str&quot;)
            .map(s -&amp;gt; {
                System.out.println(s);
                return s;
            })
            .switchIfEmpty(Mono.defer(this::lazyDefaultStr))
            .subscribe();
}

@Test
void switchIfEmptyTest3() {
    Mono.just(&quot;str&quot;)
            .map(s -&amp;gt; {
                System.out.println(s);
                return s;
            })
            .switchIfEmpty(Mono.fromSupplier(() -&amp;gt; &quot;defaultStr&quot;))
            .subscribe();
}

private Mono&amp;lt;String&amp;gt; lazyDefaultStr() {
    System.out.println(&quot;defalutStr&quot;);
    return Mono.just(&quot;default&quot;);
}

&amp;lt;console&amp;gt;
str&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위와 같이 Mono.defer 혹은 Mono.fromSupplier를 사용하면 해당 메서드의 매개변수로 Supplier를 넘기기 때문에 미리 값을 만들어 놓지 않고, 실제 호출되는 시점으로 실행을 지연시킬 수 있다. 위 코드에서는 아예 메서드 호출자체를 하지도 않는다. 필자도 지금까지는 이러한 것을 크게 인지하지 못하고 코딩을 했었는데, 이렇게 lazy programming을 조금 생각하고 코딩하게 된다면 조금이라도 성능향상을 할 수 있지 않을까 생각이 든다.&lt;/p&gt;</description>
      <category>프로그래밍언어/Java&amp;amp;Servlet</category>
      <category>EAGER</category>
      <category>Flux</category>
      <category>java</category>
      <category>Lazy</category>
      <category>Mono</category>
      <category>Mono.defer</category>
      <category>Reactor</category>
      <category>switchIfEmpty</category>
      <category>리액터</category>
      <category>자바</category>
      <author>여성게</author>
      <guid isPermaLink="true">https://coding-start.tistory.com/372</guid>
      <comments>https://coding-start.tistory.com/372#entry372comment</comments>
      <pubDate>Wed, 29 Jul 2020 18:12:40 +0900</pubDate>
    </item>
    <item>
      <title>RabbitMQ - 레빗엠큐 개념 및 동작방식, 실습</title>
      <link>https://coding-start.tistory.com/371</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/crYKZR/btqF3FVwwXJ/hjvkf61V067ebFbbKjKgt0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/crYKZR/btqF3FVwwXJ/hjvkf61V067ebFbbKjKgt0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/crYKZR/btqF3FVwwXJ/hjvkf61V067ebFbbKjKgt0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcrYKZR%2FbtqF3FVwwXJ%2Fhjvkf61V067ebFbbKjKgt0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1595769487063&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;object&quot; data-og-title=&quot;yoonyeoseong/rabbitmq-sample&quot; data-og-description=&quot;Contribute to yoonyeoseong/rabbitmq-sample development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/yoonyeoseong/rabbitmq-sample/blob/master/src/main/java/com/levi/rabbitmq/RabbitMqConfig.java&quot; data-og-url=&quot;https://github.com/yoonyeoseong/rabbitmq-sample&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/do8WS8/hyGTnUF9nq/pf6VqhJbBOFTHLo8gVnfck/img.jpg?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400&quot;&gt;&lt;a href=&quot;https://github.com/yoonyeoseong/rabbitmq-sample/blob/master/src/main/java/com/levi/rabbitmq/RabbitMqConfig.java&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/yoonyeoseong/rabbitmq-sample/blob/master/src/main/java/com/levi/rabbitmq/RabbitMqConfig.java&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/do8WS8/hyGTnUF9nq/pf6VqhJbBOFTHLo8gVnfck/img.jpg?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;yoonyeoseong/rabbitmq-sample&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;Contribute to yoonyeoseong/rabbitmq-sample development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;오늘 포스팅할 내용은 래빗엠큐이다. 그 동안에는 카프카를 사용할 일이 많아 카프카에 대한 포스팅이 주였는데, 이번에 래빗엠큐를 사용할 일이 생겨 간단히 래빗엠큐에 대해 간단히 다루어 볼것이다.(예제 코드는 위 깃헙에 올려놓았습니다.)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;비동기 작업에 있어 큐를 사용하려면 중간에 메시지 브로커라는 개념이 존재하는데, 이러한 메시지 브로커에는 RabbitMQ, Kafka 등이 대표적으로 있다. 해당 포스트에서는 표준 MQ프로토콜인&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;i&gt;&lt;b&gt;AMQP&lt;/b&gt;&lt;/i&gt;를 구현한 RabbitMQ(래빗엠큐)에 대해 다루어볼 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;간단하게 메시지큐는 아래 그림과 같은 워크 플로우로 이루어져있다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dCzxoM/btqF3csvxl4/mJeuayXS5LtozuDMw4n2r0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dCzxoM/btqF3csvxl4/mJeuayXS5LtozuDMw4n2r0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dCzxoM/btqF3csvxl4/mJeuayXS5LtozuDMw4n2r0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdCzxoM%2FbtqF3csvxl4%2FmJeuayXS5LtozuDMw4n2r0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;대부분의 메시지큐는 프로듀서가 있고, 해당 프로듀서가 브로커로 메시지를 발행하면, 적절한 컨슈머가 해당 메시지를 구독(읽다)하는 구조이다. 그렇다면 래빗엠큐는 상세하게 어떠한 구조로 되어있을까?&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c1jNIo/btqF3Gz7CXx/E5DgxajmcBCNjN25eELvhK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c1jNIo/btqF3Gz7CXx/E5DgxajmcBCNjN25eELvhK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c1jNIo/btqF3Gz7CXx/E5DgxajmcBCNjN25eELvhK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc1jNIo%2FbtqF3Gz7CXx%2FE5DgxajmcBCNjN25eELvhK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;래빗엠큐는 단순히 프로듀서가 브로커로 메시지를 전달하여 컨슈머가 큐를 읽어가는 구조라는 면에서는 동일하지만, 프로듀싱하는 과정에서 조금 더 복잡한 개념이 들어간다. 바로 exchange와 route, queue라는 개념이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;간단하게 워크 플로우를 설명하자면 아래와 같다.&lt;/p&gt;
&lt;p&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;i&gt;&lt;b&gt;Producer는 Message를 Exchange에게 보내게 됩니다.&lt;/b&gt;&lt;/i&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;i&gt;&lt;b&gt;Exchange를 생성할때 Exchange의 Type을 정해야 합니다.&lt;/b&gt;&lt;/i&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;i&gt;&lt;b&gt;Exchange는 Routing Key를 사용하여 적절한 Queue로 Routing을 진행합니다.&lt;/b&gt;&lt;/i&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;i&gt;&lt;b&gt;Routing은 Exchange Type에 따라 전략이 바뀌게 됩니다.&lt;/b&gt;&lt;/i&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;i&gt;&lt;b&gt;Exchange - Queue와 Binding이 완료된 모습을 볼 수 있습니다.&lt;/b&gt;&lt;/i&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;i&gt;&lt;b&gt;Message 속성에 따라 적절한 Queue로 Routing이 됩니다.&lt;/b&gt;&lt;/i&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;i&gt;&lt;b&gt;Message는 Consumer가 소비할때까지 Queue에 대기하게 됩니다.&lt;/b&gt;&lt;/i&gt;&lt;/li&gt;
&lt;li&gt;&lt;i&gt;&lt;b&gt;Consumer는 Message를 소비하게 됩니다.&lt;/b&gt;&lt;/i&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위에서 1번에 Exchange라는 개념이 등장하는데, Exchange는 정해진 규칙으로 메시지를 라우팅하는 기능을 가지고 있다. 여기서 정해진 규칙은 크게 4가지가 존재하는데, 규칙들은 아래와 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-size=&quot;size14&quot; data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;i&gt;exchange routeing 전략&lt;/i&gt;&lt;br /&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JPTB4/btqF2RB74O0/L6kDxo3khmIwGU5TVkBCb0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JPTB4/btqF2RB74O0/L6kDxo3khmIwGU5TVkBCb0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JPTB4/btqF2RB74O0/L6kDxo3khmIwGU5TVkBCb0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJPTB4%2FbtqF2RB74O0%2FL6kDxo3khmIwGU5TVkBCb0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Direct Exchange
&lt;ul&gt;
&lt;li&gt;Message의 Routing Key와 정확히 일치하는 Binding된 Queue로 Routing&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Fanout Exchange
&lt;ul&gt;
&lt;li&gt;Binding된 모든 Queue에 Message를 Routing&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Topic Exchange
&lt;ul&gt;
&lt;li&gt;특정 Routing Pattern이 일치하는 Queue로 Routing&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Headers Exchange
&lt;ul&gt;
&lt;li&gt;key-value로 정의된 Header 속성을 통한 Routing&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/o1nJR/btqF3G1bYuh/ILeo0XWZ1TpFb4EhWGFB1K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/o1nJR/btqF3G1bYuh/ILeo0XWZ1TpFb4EhWGFB1K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/o1nJR/btqF3G1bYuh/ILeo0XWZ1TpFb4EhWGFB1K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fo1nJR%2FbtqF3G1bYuh%2FILeo0XWZ1TpFb4EhWGFB1K%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;익스체인지는 위와 같은 전략으로 메시지를 라우팅하게 된다. 그리고 자주 사용되는 Exchange의 옵션으로는 아래와 같이 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;&lt;b&gt;exchange options&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;Durability&lt;/b&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;브로커가 재시작 될 때 남아 있는지 여부&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;durable -&amp;gt; 재시작해도 유지가능&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;transient -&amp;gt; 재시작하면 사라집니다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Auto-delete&lt;/b&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;마지막 Queue 연결이 해제되면 삭제&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;마지막으로 래빗엠큐에서 사용되는 용어가 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;Vhost(virutal host)&lt;/b&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;Virtual Host를 통해서 하나의 RabbitMQ 인스턴스 안에 사용하고 있는 Application을 분리할 수 있습니다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Connection&lt;/b&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;물리적인 TCP Connection, HTTPS -&amp;gt; TLS(SSL) Connection을 사용&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Channel&lt;/b&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;하나의 물리적인 Connection 내에 생성되는 가상의 Connection&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Consumer의 process나 thread는 각자 Channel을 통해 Queue에 연결 될 수 있습니다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;여기까지 간단하게 래빗엠큐의 동작과 용어에 대해서 다루어봤으니 실제 코드 레벨로 실습을 진행해본다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;docker rabbitmq 설치&lt;/blockquote&gt;
&lt;pre id=&quot;code_1595752567660&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo docker run -d --name rabbitmq -p 5672:5672 -p 15672:15672 \
	--restart=unless-stopped -e RABBITMQ_DEFAULT_USER=username \
    -e RABBITMQ_DEFAULT_PASS=password rabbitmq:management&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위처럼 도커로 래빗엠큐를 설치해주고, &lt;i&gt;&lt;b&gt;http://localhost:15672(username/password)&lt;/b&gt;&lt;/i&gt;로 접속해보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;springboot rabbitmq 예제&lt;/blockquote&gt;
&lt;p&gt;build.gradle&lt;/p&gt;
&lt;pre id=&quot;code_1595761769601&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;plugins {
    id 'org.springframework.boot' version '2.3.2.RELEASE'
    id 'io.spring.dependency-management' version '1.0.9.RELEASE'
    id 'java'
}

group = 'com.levi'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '14'

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-amqp'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
    annotationProcessor &quot;org.springframework.boot:spring-boot-configuration-processor&quot;
    testImplementation('org.springframework.boot:spring-boot-starter-test') {
        exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
    }
    testImplementation 'org.springframework.amqp:spring-rabbit-test'
}

test {
    useJUnitPlatform()
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;application.yml&lt;/p&gt;
&lt;pre id=&quot;code_1595761789289&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;rabbitmq:
  test:
    username: username
    password: password
    host: localhost
    port: 5672
    virtualHost: levi.vhost
    routeKey: test.route
    exchangeName: test.exchange
    queueName: testQueue
    deadLetterExchange: dead.letter.exchange
    deadLetterRouteKey: dead.letter.route&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;application.yml에서 프로퍼티를 읽어오는 클래스를 아래 하나 만들었다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;colorscripter-code&quot; style=&quot;color: #010101; font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace !important; position: relative !important; overflow: auto;&quot;&gt;
&lt;table class=&quot;colorscripter-code-table&quot; style=&quot;margin: 0; padding: 0; border: none; background-color: #fafafa; border-radius: 4px;&quot; cellspacing=&quot;0&quot; cellpadding=&quot;0&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;padding: 6px; border-right: 2px solid #e5e5e5;&quot;&gt;
&lt;div style=&quot;margin: 0; padding: 0; word-break: normal; text-align: right; color: #666; font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace !important; line-height: 130%;&quot;&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;1&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;2&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;3&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;4&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;5&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;6&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;7&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;8&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;9&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;10&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;11&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;12&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;13&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;14&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;15&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;16&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;17&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;18&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;19&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;20&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;21&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;22&lt;/div&gt;
&lt;/div&gt;
&lt;/td&gt;
&lt;td style=&quot;padding: 6px 0; text-align: left;&quot;&gt;
&lt;div style=&quot;margin: 0; padding: 0; color: #010101; font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace !important; line-height: 130%;&quot;&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;@Configuration&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;@ConfigurationProperties(prefix&amp;nbsp;=&amp;nbsp;&quot;rabbitmq&quot;)&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;@Getter&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;@Setter&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;public&amp;nbsp;class&amp;nbsp;RabbitMqProperty&amp;nbsp;{&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;RabbitMqDetailProperty&amp;nbsp;test;&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Getter&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Setter&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;static&amp;nbsp;class&amp;nbsp;RabbitMqDetailProperty&amp;nbsp;{&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;String&amp;nbsp;username;&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;String&amp;nbsp;password;&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;String&amp;nbsp;host;&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;int&amp;nbsp;port;&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;String&amp;nbsp;virtualHost;&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;String&amp;nbsp;routeKey;&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;String&amp;nbsp;exchangeName;&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;String&amp;nbsp;queueName;&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;String&amp;nbsp;deadLetterExchange;&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;String&amp;nbsp;deadLetterRouteKey;&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;}&lt;/div&gt;
&lt;/div&gt;
&lt;div style=&quot;text-align: right; margin-top: -13px; margin-right: 5px; font-size: 9px; font-style: italic;&quot;&gt;&lt;a style=&quot;color: #e5e5e5text-decoration:none;&quot; href=&quot;http://colorscripter.com/info#e&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Colored by Color Scripter&lt;/a&gt;&lt;/div&gt;
&lt;/td&gt;
&lt;td style=&quot;vertical-align: bottom; padding: 0 2px 4px 0;&quot;&gt;&lt;a style=&quot;text-decoration: none; color: white;&quot; href=&quot;http://colorscripter.com/info#e&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span style=&quot;font-size: 9px; word-break: normal; background-color: #e5e5e5; color: white; border-radius: 10px; padding: 1px;&quot;&gt;cs&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;colorscripter-code&quot; style=&quot;color: #010101; font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace !important; position: relative !important; overflow: auto;&quot;&gt;
&lt;table class=&quot;colorscripter-code-table&quot; style=&quot;margin: 0; padding: 0; border: none; background-color: #fafafa; border-radius: 4px;&quot; cellspacing=&quot;0&quot; cellpadding=&quot;0&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;padding: 6px; border-right: 2px solid #e5e5e5;&quot;&gt;
&lt;div style=&quot;margin: 0; padding: 0; word-break: normal; text-align: right; color: #666; font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace !important; line-height: 130%;&quot;&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;1&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;2&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;3&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;4&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;5&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;6&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;7&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;8&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;9&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;10&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;11&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;12&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;13&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;14&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;15&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;16&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;17&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;18&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;19&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;20&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;21&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;22&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;23&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;24&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;25&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;26&lt;/div&gt;
&lt;/div&gt;
&lt;/td&gt;
&lt;td style=&quot;padding: 6px 0; text-align: left;&quot;&gt;
&lt;div style=&quot;margin: 0; padding: 0; color: #010101; font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace !important; line-height: 130%;&quot;&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;@SpringBootApplication(exclude&amp;nbsp;&lt;span style=&quot;color: #0086b3;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #a71d5d;&quot;&gt;=&lt;/span&gt;&amp;nbsp;{&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;RabbitAutoConfiguration.&lt;span style=&quot;color: #a71d5d;&quot;&gt;class&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;})&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;@EnableRabbit&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;@RestController&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;@RequiredArgsConstructor&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&lt;span style=&quot;color: #a71d5d;&quot;&gt;public&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: #a71d5d;&quot;&gt;class&lt;/span&gt;&amp;nbsp;RabbitmqApplication&amp;nbsp;{&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: #a71d5d;&quot;&gt;private&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: #a71d5d;&quot;&gt;final&lt;/span&gt;&amp;nbsp;RabbitTemplate&amp;nbsp;testTemplate;&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: #a71d5d;&quot;&gt;public&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: #a71d5d;&quot;&gt;static&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: #a71d5d;&quot;&gt;void&lt;/span&gt;&amp;nbsp;main(&lt;span style=&quot;color: #066de2;&quot;&gt;String&lt;/span&gt;[]&amp;nbsp;args)&amp;nbsp;{&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SpringApplication.run(RabbitmqApplication.&lt;span style=&quot;color: #a71d5d;&quot;&gt;class&lt;/span&gt;,&amp;nbsp;args);&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@GetMapping(&lt;span style=&quot;color: #63a35c;&quot;&gt;&quot;/&quot;&lt;/span&gt;)&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: #a71d5d;&quot;&gt;public&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: #066de2;&quot;&gt;String&lt;/span&gt;&amp;nbsp;inQueue()&amp;nbsp;{&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;testTemplate.convertAndSend(SampleMessage.of(&lt;span style=&quot;color: #63a35c;&quot;&gt;&quot;message!!&quot;&lt;/span&gt;));&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: #a71d5d;&quot;&gt;return&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: #63a35c;&quot;&gt;&quot;success&quot;&lt;/span&gt;;&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Data&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@AllArgsConstructor(staticName&amp;nbsp;&lt;span style=&quot;color: #0086b3;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #a71d5d;&quot;&gt;=&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: #63a35c;&quot;&gt;&quot;of&quot;&lt;/span&gt;)&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@NoArgsConstructor&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: #a71d5d;&quot;&gt;public&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: #a71d5d;&quot;&gt;static&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: #a71d5d;&quot;&gt;class&lt;/span&gt;&amp;nbsp;SampleMessage&amp;nbsp;{&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: #a71d5d;&quot;&gt;private&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: #066de2;&quot;&gt;String&lt;/span&gt;&amp;nbsp;message;&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;}&lt;/div&gt;
&lt;/div&gt;
&lt;div style=&quot;text-align: right; margin-top: -13px; margin-right: 5px; font-size: 9px; font-style: italic;&quot;&gt;&lt;a style=&quot;color: #e5e5e5text-decoration:none;&quot; href=&quot;http://colorscripter.com/info#e&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Colored by Color Scripter&lt;/a&gt;&lt;/div&gt;
&lt;/td&gt;
&lt;td style=&quot;vertical-align: bottom; padding: 0 2px 4px 0;&quot;&gt;&lt;a style=&quot;text-decoration: none; color: white;&quot; href=&quot;http://colorscripter.com/info#e&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span style=&quot;font-size: 9px; word-break: normal; background-color: #e5e5e5; color: white; border-radius: 10px; padding: 1px;&quot;&gt;cs&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;래빗엠큐 auto configuration을 꺼주었고, 메시지를 인큐하기 위한 컨트롤러를 하나 만들어 주었다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;colorscripter-code&quot; style=&quot;color: #010101; font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace !important; position: relative !important; overflow: auto;&quot;&gt;
&lt;table class=&quot;colorscripter-code-table&quot; style=&quot;margin: 0; padding: 0; border: none; background-color: #fafafa; border-radius: 4px;&quot; cellspacing=&quot;0&quot; cellpadding=&quot;0&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;padding: 6px; border-right: 2px solid #e5e5e5;&quot;&gt;
&lt;div style=&quot;margin: 0; padding: 0; word-break: normal; text-align: right; color: #666; font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace !important; line-height: 130%;&quot;&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;1&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;2&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;3&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;4&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;5&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;6&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;7&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;8&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;9&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;10&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;11&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;12&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;13&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;14&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;15&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;16&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;17&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;18&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;19&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;20&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;21&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;22&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;23&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;24&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;25&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;26&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;27&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;28&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;29&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;30&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;31&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;32&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;33&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;34&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;35&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;36&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;37&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;38&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;39&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;40&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;41&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;42&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;43&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;44&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;45&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;46&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;47&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;48&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;49&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;50&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;51&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;52&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;53&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;54&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;55&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;56&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;57&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;58&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;59&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;60&lt;/div&gt;
&lt;/div&gt;
&lt;/td&gt;
&lt;td style=&quot;padding: 6px 0; text-align: left;&quot;&gt;
&lt;div style=&quot;margin: 0; padding: 0; color: #010101; font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace !important; line-height: 130%;&quot;&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;@Slf4j&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;@Configuration&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;@RequiredArgsConstructor&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&lt;span style=&quot;color: #a71d5d;&quot;&gt;public&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: #a71d5d;&quot;&gt;class&lt;/span&gt;&amp;nbsp;RabbitMqConfig&amp;nbsp;{&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: #a71d5d;&quot;&gt;private&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: #a71d5d;&quot;&gt;final&lt;/span&gt;&amp;nbsp;RabbitMqProperty&amp;nbsp;rabbitMqProperty;&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Bean&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: #a71d5d;&quot;&gt;public&lt;/span&gt;&amp;nbsp;ConnectionFactory&amp;nbsp;testConnectionFactory()&amp;nbsp;{&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: #a71d5d;&quot;&gt;final&lt;/span&gt;&amp;nbsp;RabbitMqProperty.RabbitMqDetailProperty&amp;nbsp;test&amp;nbsp;&lt;span style=&quot;color: #0086b3;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #a71d5d;&quot;&gt;=&lt;/span&gt;&amp;nbsp;rabbitMqProperty.getTest();&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: #a71d5d;&quot;&gt;final&lt;/span&gt;&amp;nbsp;CachingConnectionFactory&amp;nbsp;factory&amp;nbsp;&lt;span style=&quot;color: #0086b3;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #a71d5d;&quot;&gt;=&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: #a71d5d;&quot;&gt;new&lt;/span&gt;&amp;nbsp;CachingConnectionFactory();&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;factory.setHost(test.getHost());&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;factory.setPort(test.getPort());&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;factory.setUsername(test.getUsername());&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;factory.setPassword(test.getPassword());&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;factory.setVirtualHost(test.getVirtualHost());&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: #a71d5d;&quot;&gt;return&lt;/span&gt;&amp;nbsp;factory;&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Bean&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: #a71d5d;&quot;&gt;public&lt;/span&gt;&amp;nbsp;SimpleRabbitListenerContainerFactory&amp;nbsp;testContainer(&lt;span style=&quot;color: #a71d5d;&quot;&gt;final&lt;/span&gt;&amp;nbsp;ConnectionFactory&amp;nbsp;testConnectionFactory)&amp;nbsp;{&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: #a71d5d;&quot;&gt;final&lt;/span&gt;&amp;nbsp;SimpleRabbitListenerContainerFactory&amp;nbsp;factory&amp;nbsp;&lt;span style=&quot;color: #0086b3;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #a71d5d;&quot;&gt;=&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: #a71d5d;&quot;&gt;new&lt;/span&gt;&amp;nbsp;SimpleRabbitListenerContainerFactory();&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;factory.setConnectionFactory(testConnectionFactory);&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;factory.setConnectionFactory(testConnectionFactory);&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;factory.setErrorHandler(t&amp;nbsp;&lt;span style=&quot;color: #0086b3;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #a71d5d;&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: #0086b3;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #a71d5d;&quot;&gt;&amp;gt;&lt;/span&gt;&amp;nbsp;log.error(&lt;span style=&quot;color: #63a35c;&quot;&gt;&quot;ErrorHandler&amp;nbsp;=&amp;nbsp;{}&quot;&lt;/span&gt;,&amp;nbsp;t.getMessage()));&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;factory.setDefaultRequeueRejected(&lt;span style=&quot;color: #0099cc;&quot;&gt;false&lt;/span&gt;);&amp;nbsp;&lt;span style=&quot;color: #999999;&quot;&gt;//true일&amp;nbsp;경우&amp;nbsp;리스너에서&amp;nbsp;예외가&amp;nbsp;발생시&amp;nbsp;다시&amp;nbsp;큐에&amp;nbsp;메시지가&amp;nbsp;쌓이게&amp;nbsp;된다.(예외상황&amp;nbsp;해결안될&amp;nbsp;시&amp;nbsp;무한루프)&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;factory.setMessageConverter(jacksonConverter());&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: #999999;&quot;&gt;//예외 발생시 recover할 수 있는 옵션, 위 에러 핸들러와 잘 구분해서 사용해야할듯&lt;br /&gt;//위 핸들러와 적용 순서등도 테스트가 필요(혹은 둘다 동시에 적용되나?)&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&lt;span style=&quot;color: #999999;&quot;&gt;//&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;factory.setAdviceChain(&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&lt;span style=&quot;color: #999999;&quot;&gt;//&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;RetryInterceptorBuilder&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&lt;span style=&quot;color: #999999;&quot;&gt;//&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.stateless()&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&lt;span style=&quot;color: #999999;&quot;&gt;//&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.maxAttempts(3)&amp;nbsp;//최대&amp;nbsp;재시도&amp;nbsp;횟수&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&lt;span style=&quot;color: #999999;&quot;&gt;//&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.recoverer()&amp;nbsp;//recover&amp;nbsp;handler&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&lt;span style=&quot;color: #999999;&quot;&gt;//&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.backOffOptions(2000,&amp;nbsp;4,&amp;nbsp;10000)&amp;nbsp;//2초&amp;nbsp;간격으로,&amp;nbsp;4번,&amp;nbsp;최대&amp;nbsp;10초&amp;nbsp;내에&amp;nbsp;재시도&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&lt;span style=&quot;color: #999999;&quot;&gt;//&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build()&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&lt;span style=&quot;color: #999999;&quot;&gt;//&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;);&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: #a71d5d;&quot;&gt;return&lt;/span&gt;&amp;nbsp;factory;&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Bean&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: #a71d5d;&quot;&gt;public&lt;/span&gt;&amp;nbsp;RabbitTemplate&amp;nbsp;testTemplate(&lt;span style=&quot;color: #a71d5d;&quot;&gt;final&lt;/span&gt;&amp;nbsp;ConnectionFactory&amp;nbsp;testConnectionFactory,&amp;nbsp;&lt;span style=&quot;color: #a71d5d;&quot;&gt;final&lt;/span&gt;&amp;nbsp;MessageConverter&amp;nbsp;jacksonConverter)&amp;nbsp;{&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: #a71d5d;&quot;&gt;final&lt;/span&gt;&amp;nbsp;RabbitMqProperty.RabbitMqDetailProperty&amp;nbsp;property&amp;nbsp;&lt;span style=&quot;color: #0086b3;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #a71d5d;&quot;&gt;=&lt;/span&gt;&amp;nbsp;rabbitMqProperty.getTest();&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: #a71d5d;&quot;&gt;final&lt;/span&gt;&amp;nbsp;RabbitTemplate&amp;nbsp;template&amp;nbsp;&lt;span style=&quot;color: #0086b3;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #a71d5d;&quot;&gt;=&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: #a71d5d;&quot;&gt;new&lt;/span&gt;&amp;nbsp;RabbitTemplate();&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;template.setConnectionFactory(testConnectionFactory);&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;template.setMessageConverter(jacksonConverter);&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;template.setExchange(property.getExchangeName());&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;template.setRoutingKey(property.getRouteKey());&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: #a71d5d;&quot;&gt;return&lt;/span&gt;&amp;nbsp;template;&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Bean&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: #a71d5d;&quot;&gt;public&lt;/span&gt;&amp;nbsp;MessageConverter&amp;nbsp;jacksonConverter()&amp;nbsp;{&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: #a71d5d;&quot;&gt;return&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: #a71d5d;&quot;&gt;new&lt;/span&gt;&amp;nbsp;Jackson2JsonMessageConverter();&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&lt;br /&gt;//app에서 큐만들거나 바인딩 하기 위해서는 RabbitAdmin 필요&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Bean&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: #a71d5d;&quot;&gt;public&lt;/span&gt;&amp;nbsp;RabbitAdmin&amp;nbsp;testAdmin(&lt;span style=&quot;color: #a71d5d;&quot;&gt;final&lt;/span&gt;&amp;nbsp;ConnectionFactory&amp;nbsp;testConnectionFactory)&amp;nbsp;{&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: #a71d5d;&quot;&gt;return&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: #a71d5d;&quot;&gt;new&lt;/span&gt;&amp;nbsp;RabbitAdmin(testConnectionFactory);&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;}&lt;/div&gt;
&lt;/div&gt;
&lt;div style=&quot;text-align: right; margin-top: -13px; margin-right: 5px; font-size: 9px; font-style: italic;&quot;&gt;&lt;a style=&quot;color: #e5e5e5text-decoration:none;&quot; href=&quot;http://colorscripter.com/info#e&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Colored by Color Scripter&lt;/a&gt;&lt;/div&gt;
&lt;/td&gt;
&lt;td style=&quot;vertical-align: bottom; padding: 0 2px 4px 0;&quot;&gt;&lt;a style=&quot;text-decoration: none; color: white;&quot; href=&quot;http://colorscripter.com/info#e&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span style=&quot;font-size: 9px; word-break: normal; background-color: #e5e5e5; color: white; border-radius: 10px; padding: 1px;&quot;&gt;cs&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;브로커 연결 및 메시지를 프로듀싱하기 위한 RabbitTemplate 설정이다. 이중 RabbitAdmin 빈을 만들고 있는 수동이 아니라, 앱에서 큐를 만들고 바인딩 하기 위해서는 RabbitAdmin이 빈으로 떠있어야한다. 기타 설정은 주석을 참고하자 !&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;colorscripter-code&quot; style=&quot;color: #010101; font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace !important; position: relative !important; overflow: auto;&quot;&gt;
&lt;table class=&quot;colorscripter-code-table&quot; style=&quot;margin: 0; padding: 0; border: none; background-color: #fafafa; border-radius: 4px;&quot; cellspacing=&quot;0&quot; cellpadding=&quot;0&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;padding: 6px; border-right: 2px solid #e5e5e5;&quot;&gt;
&lt;div style=&quot;margin: 0; padding: 0; word-break: normal; text-align: right; color: #666; font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace !important; line-height: 130%;&quot;&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;1&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;2&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;3&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;4&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;5&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;6&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;7&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;8&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;9&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;10&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;11&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;12&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;13&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;14&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;15&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;16&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;17&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;18&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;19&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;20&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;21&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;22&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;23&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;24&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;25&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;26&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;27&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;28&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;29&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;30&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;31&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;32&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;33&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;34&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;35&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;36&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;37&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;38&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;39&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;40&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;41&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;42&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;43&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;44&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;45&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;46&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;47&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;48&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;49&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;50&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;51&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;52&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;53&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;54&lt;/div&gt;
&lt;/div&gt;
&lt;/td&gt;
&lt;td style=&quot;padding: 6px 0; text-align: left;&quot;&gt;
&lt;div style=&quot;margin: 0; padding: 0; color: #010101; font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace !important; line-height: 130%;&quot;&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;@Configuration&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;@RequiredArgsConstructor&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&lt;span style=&quot;color: #a71d5d;&quot;&gt;public&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: #a71d5d;&quot;&gt;class&lt;/span&gt;&amp;nbsp;RabbitMqBindingConfig&amp;nbsp;{&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: #a71d5d;&quot;&gt;private&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: #a71d5d;&quot;&gt;final&lt;/span&gt;&amp;nbsp;RabbitMqProperty&amp;nbsp;rabbitMqProperty;&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Bean&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: #a71d5d;&quot;&gt;public&lt;/span&gt;&amp;nbsp;TopicExchange&amp;nbsp;testTopicExchange()&amp;nbsp;{&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: #a71d5d;&quot;&gt;return&lt;/span&gt;&amp;nbsp;ExchangeBuilder&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.topicExchange(rabbitMqProperty.getTest().getExchangeName())&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.durable(&lt;span style=&quot;color: #0099cc;&quot;&gt;true&lt;/span&gt;)&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build();&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Bean&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: #a71d5d;&quot;&gt;public&lt;/span&gt;&amp;nbsp;TopicExchange&amp;nbsp;dlExchange()&amp;nbsp;{&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: #a71d5d;&quot;&gt;return&lt;/span&gt;&amp;nbsp;ExchangeBuilder&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.topicExchange(rabbitMqProperty.getTest().getDeadLetterExchange())&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.durable(&lt;span style=&quot;color: #0099cc;&quot;&gt;true&lt;/span&gt;)&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build();&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Bean&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: #a71d5d;&quot;&gt;public&lt;/span&gt;&amp;nbsp;Queue&amp;nbsp;testQueue()&amp;nbsp;{&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: #a71d5d;&quot;&gt;final&lt;/span&gt;&amp;nbsp;RabbitMqProperty.RabbitMqDetailProperty&amp;nbsp;testDetail&amp;nbsp;&lt;span style=&quot;color: #0086b3;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #a71d5d;&quot;&gt;=&lt;/span&gt;&amp;nbsp;rabbitMqProperty.getTest();&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: #a71d5d;&quot;&gt;return&lt;/span&gt;&amp;nbsp;QueueBuilder&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.durable(testDetail.getQueueName())&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.deadLetterExchange(testDetail.getDeadLetterExchange())&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.deadLetterRoutingKey(testDetail.getDeadLetterRouteKey())&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build();&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Bean&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: #a71d5d;&quot;&gt;public&lt;/span&gt;&amp;nbsp;Queue&amp;nbsp;dlq()&amp;nbsp;{&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: #a71d5d;&quot;&gt;return&lt;/span&gt;&amp;nbsp;QueueBuilder&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.durable(&lt;span style=&quot;color: #63a35c;&quot;&gt;&quot;DEAD_LETTER_QUEUE&quot;&lt;/span&gt;)&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build();&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Bean&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: #a71d5d;&quot;&gt;public&lt;/span&gt;&amp;nbsp;Binding&amp;nbsp;testBinding(&lt;span style=&quot;color: #a71d5d;&quot;&gt;final&lt;/span&gt;&amp;nbsp;Queue&amp;nbsp;testQueue,&amp;nbsp;&lt;span style=&quot;color: #a71d5d;&quot;&gt;final&lt;/span&gt;&amp;nbsp;TopicExchange&amp;nbsp;testTopicExchange)&amp;nbsp;{&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: #a71d5d;&quot;&gt;return&lt;/span&gt;&amp;nbsp;BindingBuilder&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.bind(testQueue)&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.to(testTopicExchange)&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.with(rabbitMqProperty.getTest().getRouteKey());&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Bean&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: #a71d5d;&quot;&gt;public&lt;/span&gt;&amp;nbsp;Binding&amp;nbsp;dlBinding(&lt;span style=&quot;color: #a71d5d;&quot;&gt;final&lt;/span&gt;&amp;nbsp;Queue&amp;nbsp;dlq,&amp;nbsp;&lt;span style=&quot;color: #a71d5d;&quot;&gt;final&lt;/span&gt;&amp;nbsp;TopicExchange&amp;nbsp;dlExchange)&amp;nbsp;{&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: #a71d5d;&quot;&gt;return&lt;/span&gt;&amp;nbsp;BindingBuilder&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.bind(dlq)&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.to(dlExchange)&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.with(rabbitMqProperty.getTest().getDeadLetterRouteKey());&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;}&lt;/div&gt;
&lt;/div&gt;
&lt;div style=&quot;text-align: right; margin-top: -13px; margin-right: 5px; font-size: 9px; font-style: italic;&quot;&gt;&lt;a style=&quot;color: #e5e5e5text-decoration:none;&quot; href=&quot;http://colorscripter.com/info#e&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Colored by Color Scripter&lt;/a&gt;&lt;/div&gt;
&lt;/td&gt;
&lt;td style=&quot;vertical-align: bottom; padding: 0 2px 4px 0;&quot;&gt;&lt;a style=&quot;text-decoration: none; color: white;&quot; href=&quot;http://colorscripter.com/info#e&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span style=&quot;font-size: 9px; word-break: normal; background-color: #e5e5e5; color: white; border-radius: 10px; padding: 1px;&quot;&gt;cs&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;프로듀싱과 컨슈밍을 위해 브로커에 exchange와 queue를 binding하는 설정이다. 하나 추가적으로는 컨슈밍에서 예외가 발생하였을 때, Recover를 위한 Dead Letter Queue를 선언하였다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이제 앱을 실행시키고, localhost:8080으로 요청을 보내보자 ! 요청을 보내면 메시지를 프로듀싱 할것이고, 해당 메시지를 컨슈밍해서 로그를 찍게 될 것이다. 여기까지 간단하게 래빗엠큐에 대해 다루어봤다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://jonnung.dev/rabbitmq/2019/02/06/about-amqp-implementtation-of-rabbitmq/&quot;&gt;https://jonnung.dev/rabbitmq/2019/02/06/about-amqp-implementtation-of-rabbitmq/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1595752058617&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;website&quot; data-og-title=&quot;조은우 개발 블로그&quot; data-og-description=&quot; &quot; data-og-host=&quot;jonnung.dev&quot; data-og-source-url=&quot;https://jonnung.dev/rabbitmq/2019/02/06/about-amqp-implementtation-of-rabbitmq/&quot; data-og-url=&quot;https://jonnung.dev&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://jonnung.dev/rabbitmq/2019/02/06/about-amqp-implementtation-of-rabbitmq/&quot; data-source-url=&quot;https://jonnung.dev/rabbitmq/2019/02/06/about-amqp-implementtation-of-rabbitmq/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;조은우 개발 블로그&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;jonnung.dev&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&lt;a href=&quot;https://velog.io/@hellozin/Spring-Boot%EC%99%80-RabbitMQ-%EC%B4%88%EA%B0%84%EB%8B%A8-%EC%84%A4%EB%AA%85%EC%84%9C&quot;&gt;https://velog.io/@hellozin/Spring-Boot%EC%99%80-RabbitMQ-%EC%B4%88%EA%B0%84%EB%8B%A8-%EC%84%A4%EB%AA%85%EC%84%9C&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1595752058618&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Spring Boot와 RabbitMQ 초간단 설명서&quot; data-og-description=&quot;이번 포스트에서는 Spring boot 프로젝트에서 RabbitMQ를 사용하는 간단한 방법을 알아보겠습니다. Consumer 코드와 Producer 코드는 GitHub에 있습니다. 먼저 RabbitMQ 서버를 실행해야 하는데 Docker를 사용하�&quot; data-og-host=&quot;velog.io&quot; data-og-source-url=&quot;https://velog.io/@hellozin/Spring-Boot%EC%99%80-RabbitMQ-%EC%B4%88%EA%B0%84%EB%8B%A8-%EC%84%A4%EB%AA%85%EC%84%9C&quot; data-og-url=&quot;https://velog.io/@hellozin/Spring-Boot와-RabbitMQ-초간단-설명서&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/eZqYf/hyGDaeMizl/BuIyqhErqDCRgTBxavPdoK/img.png?width=950&amp;amp;height=500&amp;amp;face=0_0_950_500,https://scrap.kakaocdn.net/dn/iwTcq/hyGC8Vy7M2/KuQQG9bftVebfKGPrFtTnk/img.jpg?width=240&amp;amp;height=240&amp;amp;face=52_85_166_199&quot;&gt;&lt;a href=&quot;https://velog.io/@hellozin/Spring-Boot%EC%99%80-RabbitMQ-%EC%B4%88%EA%B0%84%EB%8B%A8-%EC%84%A4%EB%AA%85%EC%84%9C&quot; data-source-url=&quot;https://velog.io/@hellozin/Spring-Boot%EC%99%80-RabbitMQ-%EC%B4%88%EA%B0%84%EB%8B%A8-%EC%84%A4%EB%AA%85%EC%84%9C&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/eZqYf/hyGDaeMizl/BuIyqhErqDCRgTBxavPdoK/img.png?width=950&amp;amp;height=500&amp;amp;face=0_0_950_500,https://scrap.kakaocdn.net/dn/iwTcq/hyGC8Vy7M2/KuQQG9bftVebfKGPrFtTnk/img.jpg?width=240&amp;amp;height=240&amp;amp;face=52_85_166_199');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;Spring Boot와 RabbitMQ 초간단 설명서&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;이번 포스트에서는 Spring boot 프로젝트에서 RabbitMQ를 사용하는 간단한 방법을 알아보겠습니다. Consumer 코드와 Producer 코드는 GitHub에 있습니다. 먼저 RabbitMQ 서버를 실행해야 하는데 Docker를 사용하�&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;velog.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&lt;a href=&quot;https://nesoy.github.io/articles/2019-02/RabbitMQ&quot;&gt;https://nesoy.github.io/articles/2019-02/RabbitMQ&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1595752058618&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;article&quot; data-og-title=&quot;RabbitMQ에 대해&quot; data-og-description=&quot; &quot; data-og-host=&quot;nesoy.github.io&quot; data-og-source-url=&quot;https://nesoy.github.io/articles/2019-02/RabbitMQ&quot; data-og-url=&quot;https://nesoy.github.io/articles/2019-02/RabbitMQ&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/iBUi2/hyGBQI0C2m/cbIu5JDmy7TnKTUoH6KqtK/img.png?width=725&amp;amp;height=270&amp;amp;face=0_0_725_270,https://scrap.kakaocdn.net/dn/btC9uP/hyGDcXYu87/JbE1aPd4UA0K3kKKFT4MFk/img.png?width=820&amp;amp;height=640&amp;amp;face=0_0_820_640,https://scrap.kakaocdn.net/dn/dB3jaw/hyGDiRqhgG/5oDYesibX5Av9fbvsMkPg0/img.png?width=800&amp;amp;height=110&amp;amp;face=0_0_800_110&quot;&gt;&lt;a href=&quot;https://nesoy.github.io/articles/2019-02/RabbitMQ&quot; data-source-url=&quot;https://nesoy.github.io/articles/2019-02/RabbitMQ&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/iBUi2/hyGBQI0C2m/cbIu5JDmy7TnKTUoH6KqtK/img.png?width=725&amp;amp;height=270&amp;amp;face=0_0_725_270,https://scrap.kakaocdn.net/dn/btC9uP/hyGDcXYu87/JbE1aPd4UA0K3kKKFT4MFk/img.png?width=820&amp;amp;height=640&amp;amp;face=0_0_820_640,https://scrap.kakaocdn.net/dn/dB3jaw/hyGDiRqhgG/5oDYesibX5Av9fbvsMkPg0/img.png?width=800&amp;amp;height=110&amp;amp;face=0_0_800_110');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;RabbitMQ에 대해&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;nesoy.github.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Middleware/Kafka&amp;amp;RabbitMQ</category>
      <category>AMQP</category>
      <category>EXCHANGE</category>
      <category>Message Queue</category>
      <category>MSA</category>
      <category>Queue</category>
      <category>RabbitMQ</category>
      <category>래빗엠큐</category>
      <category>메시지큐</category>
      <category>비동기</category>
      <category>큐</category>
      <author>여성게</author>
      <guid isPermaLink="true">https://coding-start.tistory.com/371</guid>
      <comments>https://coding-start.tistory.com/371#entry371comment</comments>
      <pubDate>Sun, 26 Jul 2020 17:32:59 +0900</pubDate>
    </item>
    <item>
      <title>Java - 자바 클로져(Closure) &amp;amp; 커링(currying)</title>
      <link>https://coding-start.tistory.com/370</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Twg7G/btqFXhmS3yW/tUItpqK8HLklqjgGEf27V1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Twg7G/btqFXhmS3yW/tUItpqK8HLklqjgGEf27V1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Twg7G/btqFXhmS3yW/tUItpqK8HLklqjgGEf27V1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTwg7G%2FbtqFXhmS3yW%2FtUItpqK8HLklqjgGEf27V1%2Fimg.jpg&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;오늘은 클로저(Closure)와 커링(Currying)에 대해 다루어본다. 사실 이전에 자바스크립트를 간단히 공부하면서 봤던 기억이 있는 개념이었는데, 사실 정확한 개념을 알지 못하고 사용했던 것 같은데 이번에 정리해본다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;&lt;b&gt;클로저(Closure)&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;p&gt;클로저는 내부함수가 외부함수의 맥락(context)에 접근할 수 있는 것을 뜻한다. 그 뜻은 외부 함수안에 있는 내부 함수가 외부함수의 지역변수를 사용할 수 있다라는 뜻이다. 특이한 것은 외부 함수가 종료되더라도 내부함수에서 참조하는 외부함수의 context는 유지 된다는 것이다. 그것을 간단하게 자바 코드로 짜면 아래와 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;colorscripter-code&quot; style=&quot;color: #010101; font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace !important; position: relative !important; overflow: auto;&quot;&gt;
&lt;table class=&quot;colorscripter-code-table&quot; style=&quot;margin: 0; padding: 0; border: none; background-color: #fafafa; border-radius: 4px;&quot; cellspacing=&quot;0&quot; cellpadding=&quot;0&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;padding: 6px; border-right: 2px solid #e5e5e5;&quot;&gt;
&lt;div style=&quot;margin: 0; padding: 0; word-break: normal; text-align: right; color: #666; font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace !important; line-height: 130%;&quot;&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;1&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;2&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;3&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;4&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;5&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;6&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;7&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;8&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;9&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;10&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;11&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;12&lt;/div&gt;
&lt;/div&gt;
&lt;/td&gt;
&lt;td style=&quot;padding: 6px 0; text-align: left;&quot;&gt;
&lt;div style=&quot;margin: 0; padding: 0; color: #010101; font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace !important; line-height: 130%;&quot;&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&lt;span style=&quot;color: #a71d5d;&quot;&gt;public&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: #a71d5d;&quot;&gt;class&lt;/span&gt;&amp;nbsp;closure&amp;nbsp;{&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Test&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: #a71d5d;&quot;&gt;void&lt;/span&gt;&amp;nbsp;closure()&amp;nbsp;{&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: #a71d5d;&quot;&gt;final&lt;/span&gt;&amp;nbsp;var&amp;nbsp;supplier&amp;nbsp;&lt;span style=&quot;color: #0086b3;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #a71d5d;&quot;&gt;=&lt;/span&gt;&amp;nbsp;outerMethod();&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: #066de2;&quot;&gt;System&lt;/span&gt;.&lt;span style=&quot;color: #066de2;&quot;&gt;out&lt;/span&gt;.&lt;span style=&quot;color: #066de2;&quot;&gt;println&lt;/span&gt;(supplier.get());&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: #a71d5d;&quot;&gt;private&lt;/span&gt;&amp;nbsp;Supplier&lt;span style=&quot;color: #0086b3;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #a71d5d;&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color: #066de2;&quot;&gt;String&lt;/span&gt;&lt;span style=&quot;color: #a71d5d;&quot;&gt;&amp;gt;&lt;/span&gt;&amp;nbsp;outerMethod()&amp;nbsp;{&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: #a71d5d;&quot;&gt;final&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: #066de2;&quot;&gt;String&lt;/span&gt;&amp;nbsp;str&amp;nbsp;&lt;span style=&quot;color: #0086b3;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #a71d5d;&quot;&gt;=&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: #63a35c;&quot;&gt;&quot;outer&amp;nbsp;method&amp;nbsp;local&amp;nbsp;variable&quot;&lt;/span&gt;;&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: #a71d5d;&quot;&gt;return&lt;/span&gt;&amp;nbsp;()&amp;nbsp;&lt;span style=&quot;color: #0086b3;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #a71d5d;&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: #0086b3;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #a71d5d;&quot;&gt;&amp;gt;&lt;/span&gt;&amp;nbsp;str;&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;}&lt;/div&gt;
&lt;/div&gt;
&lt;div style=&quot;text-align: right; margin-top: -13px; margin-right: 5px; font-size: 9px; font-style: italic;&quot;&gt;&lt;a style=&quot;color: #e5e5e5text-decoration:none;&quot; href=&quot;http://colorscripter.com/info#e&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Colored by Color Scripter&lt;/a&gt;&lt;/div&gt;
&lt;/td&gt;
&lt;td style=&quot;vertical-align: bottom; padding: 0 2px 4px 0;&quot;&gt;&lt;a style=&quot;text-decoration: none; color: white;&quot; href=&quot;http://colorscripter.com/info#e&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span style=&quot;font-size: 9px; word-break: normal; background-color: #e5e5e5; color: white; border-radius: 10px; padding: 1px;&quot;&gt;cs&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위 코드를 보면 outerMethod는 Supplier를 반환하는데, 그 내부의 Supplier는 외부 메서드의 지역변수를 참조해 그대로 리턴하고 있다. 그리고 해당 메서드를 사용하는 @Test 메서드를 보자. outerMethod()를 호출했고 그것을 변수로 받고 있는데, 이 시점에는 outerMethod()는 종료되어 소멸되었지만, Supplier를 get하면 이미 종료된 함수의 지역변수를 그대로 출력하고 있다. 어떻게 이미 종료된 외부함수의 지역변수를 참조할 수 있는 것일까? 그 이유는 클로저가 생성되는 시점에 함수 자체가 복사되어 따로 컨텍스트를 유지하기 때문이다. 조금더 자세히 설명하면 &lt;span&gt;익명 클래스에 컨텍스트를 넘겨주는 것이 클로저다. 컴파일러는 이 필요한 정보를 복사해서 넘겨주는데 이를 &lt;u&gt;&lt;i&gt;&lt;b&gt;Variable capture&lt;/b&gt;&lt;/i&gt;&lt;/u&gt; 라고 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;자바에서 클로저가 어떻게 동작하는지 조금 더 자세히 살펴보면, 내부함수가 사용하는 외부함수의 지역변수를 클로저가 생성되는 시점에 final로 간주된다. final로 간주된다는 뜻은 새로운 인스턴스를 할당하지 못하게 되는 것이다. 1.7이전 자바는 명시적으로 final을 붙여줘야했지만 1.8 이후부터는 외부함수의 지역변수는 유사파이널로 간주되어 final를 명시적으로 붙이지 않아도 컴파일 타임에 final로 간주하게 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그리고 위 코드를 아래와 같이 변경하게 되면 컴파일 에러가 난다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;colorscripter-code&quot; style=&quot;color: #010101; font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace !important; position: relative !important; overflow: auto;&quot;&gt;
&lt;table class=&quot;colorscripter-code-table&quot; style=&quot;margin: 0; padding: 0; border: none; background-color: #fafafa; border-radius: 4px;&quot; cellspacing=&quot;0&quot; cellpadding=&quot;0&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;padding: 6px; border-right: 2px solid #e5e5e5;&quot;&gt;
&lt;div style=&quot;margin: 0; padding: 0; word-break: normal; text-align: right; color: #666; font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace !important; line-height: 130%;&quot;&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;1&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;2&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;3&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;4&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;5&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;6&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;7&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;8&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;9&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;10&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;11&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;12&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;13&lt;/div&gt;
&lt;/div&gt;
&lt;/td&gt;
&lt;td style=&quot;padding: 6px 0; text-align: left;&quot;&gt;
&lt;div style=&quot;margin: 0; padding: 0; color: #010101; font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace !important; line-height: 130%;&quot;&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;@Test&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&lt;span style=&quot;color: #a71d5d;&quot;&gt;void&lt;/span&gt;&amp;nbsp;closure()&amp;nbsp;{&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: #a71d5d;&quot;&gt;final&lt;/span&gt;&amp;nbsp;var&amp;nbsp;supplier&amp;nbsp;&lt;span style=&quot;color: #0086b3;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #a71d5d;&quot;&gt;=&lt;/span&gt;&amp;nbsp;outerMethod();&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: #066de2;&quot;&gt;System&lt;/span&gt;.&lt;span style=&quot;color: #066de2;&quot;&gt;out&lt;/span&gt;.&lt;span style=&quot;color: #066de2;&quot;&gt;println&lt;/span&gt;(supplier.get());&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;}&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&lt;span style=&quot;color: #a71d5d;&quot;&gt;private&lt;/span&gt;&amp;nbsp;Supplier&lt;span style=&quot;color: #0086b3;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #a71d5d;&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color: #066de2;&quot;&gt;String&lt;/span&gt;&lt;span style=&quot;color: #a71d5d;&quot;&gt;&amp;gt;&lt;/span&gt;&amp;nbsp;outerMethod()&amp;nbsp;{&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: #066de2;&quot;&gt;String&lt;/span&gt;&amp;nbsp;str&amp;nbsp;&lt;span style=&quot;color: #0086b3;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #a71d5d;&quot;&gt;=&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: #63a35c;&quot;&gt;&quot;outer&amp;nbsp;method&amp;nbsp;local&amp;nbsp;variable&quot;&lt;/span&gt;;&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: #a71d5d;&quot;&gt;return&lt;/span&gt;&amp;nbsp;()&amp;nbsp;&lt;span style=&quot;color: #0086b3;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #a71d5d;&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: #0086b3;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #a71d5d;&quot;&gt;&amp;gt;&lt;/span&gt;&amp;nbsp;{&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;str&amp;nbsp;&lt;span style=&quot;color: #0086b3;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #a71d5d;&quot;&gt;=&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: #63a35c;&quot;&gt;&quot;aa&quot;&lt;/span&gt;;&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: #a71d5d;&quot;&gt;return&lt;/span&gt;&amp;nbsp;str;&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;};&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;}&lt;/div&gt;
&lt;/div&gt;
&lt;div style=&quot;text-align: right; margin-top: -13px; margin-right: 5px; font-size: 9px; font-style: italic;&quot;&gt;&lt;a style=&quot;color: #e5e5e5text-decoration:none;&quot; href=&quot;http://colorscripter.com/info#e&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Colored by Color Scripter&lt;/a&gt;&lt;/div&gt;
&lt;/td&gt;
&lt;td style=&quot;vertical-align: bottom; padding: 0 2px 4px 0;&quot;&gt;&lt;a style=&quot;text-decoration: none; color: white;&quot; href=&quot;http://colorscripter.com/info#e&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span style=&quot;font-size: 9px; word-break: normal; background-color: #e5e5e5; color: white; border-radius: 10px; padding: 1px;&quot;&gt;cs&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;final로 간주되는 str 변수에 새로운 주소값을 할당하려하니 컴파일 에러가 나는 것이다. 하지만 이를 우회하는 방법으로 객체를 사용할 수 있다. 객체는 final로 생성되더라도, 안에 프로퍼티를 변경 할수 있기 때문이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그렇다면 자바에서 람다와 클로저의 차이점은 무엇일까?&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;&lt;b&gt;람다와 클로저의 차이점&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;p&gt;람다와 클로저는 모두 익명의 특정 기능 블록이고, 차이점은 클로저는 외부 변수를 참조하고, 람다는 자신이 받는 매개변수만 참조한다는 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1596349910033&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Lambda.
(server) -&amp;gt; server.isRunning();

// Closure. 외부의 server 라는 변수를 참조
() -&amp;gt; server.isRunning();&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;즉, 자바에서 클로저는 외부 변수를 참조하는 익명 클래스이고, 람다는 메서드의 매개변수만 참조하는 익명클래스가 되는 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1596350046021&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private Supplier&amp;lt;String&amp;gt; outerMethod() {
    String str = &quot;outer method local variable&quot;;
    return new Supplier&amp;lt;String&amp;gt;() {
        @Override
        public String get() {
            return str;
        }
    };
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;&lt;b&gt;커링(Currying)&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;span&gt;Currying 은 1967년 Christopher Strachey 가 Haskell Brooks Curry의 이름에서 착안한 것이다. Currying은 여러 개의 인자를 가진 함수를 호출 할 경우, 파라미터의 수보다 적은 수의 파라미터를 인자로 받으면서 누락된 파라미터를 인자로 받는 기법을 말한다. 즉 커링은 함수 하나가 n개의 인자를 받는 과정을 n개의 함수로 각각의 인자를 받도록 하는 것이다. 부분적으로 적용된 함수를 체인으로 계속 생성해 결과적으로 값을 처리하도록 하는 것이 그 본질이다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1596351267351&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private static List&amp;lt;Integer&amp;gt; calculate(List&amp;lt;Integer&amp;gt; list, Integer a) {
  return list.map(new Function&amp;lt;Integer, Function&amp;lt;Integer, Function&amp;lt;Integer, Integer&amp;gt;&amp;gt;&amp;gt;() {
    @Override
    public Function&amp;lt;Integer, Function&amp;lt;Integer, Integer&amp;gt;&amp;gt; apply(final Integer x) {
      return new Function&amp;lt;Integer, Function&amp;lt;Integer, Integer&amp;gt;&amp;gt;() {
        @Override
        public Function&amp;lt;Integer, Integer&amp;gt; apply(final Integer y) {
          return new Function&amp;lt;Integer, Integer&amp;gt;() {
            @Override
            public Integer apply(Integer t) {
              return x + y * t;
            }
          };
        }
      };
    }
  }.apply(b).apply(a));
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위와 같이 매개변수를 하나씩 받고 해당 매개변수가 일부반영된 Function을 다시 리턴하는 식으로 마지막 적용될 함수에 매개변수를 일부씩 적용시키는 것이다. 이것을 조금더 간소화 시키면,&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1596351322081&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private Stream&amp;lt;Integer&amp;gt; calculate(Stream&amp;lt;Integer&amp;gt; stream, Integer a) {
  return stream.map(((F3) x -&amp;gt; y -&amp;gt; z -&amp;gt; x + y * z).apply(b).apply(a));
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위와 같이 적용도 가능하다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://futurecreator.github.io/2018/08/09/java-lambda-and-closure/&quot;&gt;https://futurecreator.github.io/2018/08/09/java-lambda-and-closure/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1595421164276&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Java Lambda (7) 람다와 클로저&quot; data-og-description=&quot;람다와 클로저 자바 커뮤니티에서는 클로저와 람다를 혼용하면서 개념 상 혼란이 있었습니다. 그래서 자바 8부터 클로저를 지원한다는 글을 보기도 합니다. 이번 포스트에서는 자바에서의 람다&quot; data-og-host=&quot;futurecreator.github.io&quot; data-og-source-url=&quot;https://futurecreator.github.io/2018/08/09/java-lambda-and-closure/&quot; data-og-url=&quot;https://futurecreator.github.io/2018/08/09/java-lambda-and-closure/index.html&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/418aZ/hyGQIRZl0I/HtbfCWXHyOMtz8yvAJobD0/img.jpg?width=3648&amp;amp;height=2736&amp;amp;face=0_0_3648_2736,https://scrap.kakaocdn.net/dn/s84gt/hyGR1h7fah/IMMVQCz7fSfFZbsz56FZ51/img.jpg?width=3648&amp;amp;height=2736&amp;amp;face=0_0_3648_2736&quot;&gt;&lt;a href=&quot;https://futurecreator.github.io/2018/08/09/java-lambda-and-closure/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://futurecreator.github.io/2018/08/09/java-lambda-and-closure/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/418aZ/hyGQIRZl0I/HtbfCWXHyOMtz8yvAJobD0/img.jpg?width=3648&amp;amp;height=2736&amp;amp;face=0_0_3648_2736,https://scrap.kakaocdn.net/dn/s84gt/hyGR1h7fah/IMMVQCz7fSfFZbsz56FZ51/img.jpg?width=3648&amp;amp;height=2736&amp;amp;face=0_0_3648_2736');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;Java Lambda (7) 람다와 클로저&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;람다와 클로저 자바 커뮤니티에서는 클로저와 람다를 혼용하면서 개념 상 혼란이 있었습니다. 그래서 자바 8부터 클로저를 지원한다는 글을 보기도 합니다. 이번 포스트에서는 자바에서의 람다&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;futurecreator.github.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://egloos.zum.com/ryukato/v/1160506&quot;&gt;http://egloos.zum.com/ryukato/v/1160506&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1595421195096&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Java 8의 문제점: 커링(currying)대 클로져(closure))&quot; data-og-description=&quot;원문을 번역한 것입니다.Closure 예제커링 사용하기자동 커링currying의 다른 응용들정리&amp;lt;h1 style=&amp;quot;line-height: 1.6; clear: both; font-size: 2.2em; font-weight: bold; margin: 1.5em 0px 1em;&amp;quot;&amp;gt;Java 8의 문제점: 커링(currying)대&quot; data-og-host=&quot;egloos.zum.com&quot; data-og-source-url=&quot;http://egloos.zum.com/ryukato/v/1160506&quot; data-og-url=&quot;http://egloos.zum.com/ryukato/v/1160506&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bm9SSo/hyGSbyhiKO/xdHAu8PO7NPHZpDWYfEIN1/img.jpg?width=250&amp;amp;height=250&amp;amp;face=0_0_250_250&quot;&gt;&lt;a href=&quot;http://egloos.zum.com/ryukato/v/1160506&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;http://egloos.zum.com/ryukato/v/1160506&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bm9SSo/hyGSbyhiKO/xdHAu8PO7NPHZpDWYfEIN1/img.jpg?width=250&amp;amp;height=250&amp;amp;face=0_0_250_250');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;Java 8의 문제점: 커링(currying)대 클로져(closure))&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;원문을 번역한 것입니다.Closure 예제커링 사용하기자동 커링currying의 다른 응용들정리&lt;/p&gt;
&lt;h1 style=&quot;line-height: 1.6; clear: both; font-size: 2.2em; font-weight: bold; margin: 1.5em 0px 1em;&quot;&gt;Java 8의 문제점: 커링(currying)대&lt;/h1&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;egloos.zum.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>프로그래밍언어/Java&amp;amp;Servlet</category>
      <category>capture</category>
      <category>closure</category>
      <category>currying</category>
      <category>java</category>
      <category>내부함수</category>
      <category>외부함수</category>
      <category>캡쳐</category>
      <category>커링</category>
      <category>클로저</category>
      <author>여성게</author>
      <guid isPermaLink="true">https://coding-start.tistory.com/370</guid>
      <comments>https://coding-start.tistory.com/370#entry370comment</comments>
      <pubDate>Wed, 22 Jul 2020 21:38:53 +0900</pubDate>
    </item>
    <item>
      <title>Kubernetes - kubernetes(쿠버네티스) resources(cpu/momory) 할당 및 관리</title>
      <link>https://coding-start.tistory.com/369</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c8lNkj/btqFNhWXP7w/SNKApKY1PdkVzJzhvAj6b0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c8lNkj/btqFNhWXP7w/SNKApKY1PdkVzJzhvAj6b0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c8lNkj/btqFNhWXP7w/SNKApKY1PdkVzJzhvAj6b0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc8lNkj%2FbtqFNhWXP7w%2FSNKApKY1PdkVzJzhvAj6b0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;오늘 간단히 다루어볼 내용은 쿠버네티스 리소스(cpu, memory) 할당과 관리에 대한 이야기이다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;&lt;b&gt;리소스 관리&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;p&gt;쿠버네티스에서 Pod를 어느 노드에 배포할지 결정하는 것을 스케쥴링이라고 한다. 팟에 대한 스케쥴링시, 노드에 애플리케이션이 동작할 수 있는 충분한자원(CPU, 메모리 등)이 확보되어야 배포가 가능하다. 이때문에 쿠버네티스 manifast 파일에 아주 중요한 설정이 있는데, 그것은 request, limit 에 대한 설정이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;Request&amp;amp;Limit&lt;/b&gt;&lt;/blockquote&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;306&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blwHr6/btqFOMONpkS/ncAIMUUKxk9FzTnUrQLhj1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blwHr6/btqFOMONpkS/ncAIMUUKxk9FzTnUrQLhj1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blwHr6/btqFOMONpkS/ncAIMUUKxk9FzTnUrQLhj1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FblwHr6%2FbtqFOMONpkS%2FncAIMUUKxk9FzTnUrQLhj1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;306&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;컨테이너에 적용될 리소스의 양을 정의하는데, request와 limit이라는 설정을 사용한다. request는 컨테이너가 생성될때 최소한 있어야하는 자원 요청이고, limit은 request만큼 할당된 것보다 더 많은 리소스가 필요할때, 해당 컨테이너에게 최대로 줄 수 있는 자원의 양을 뜻한다. 간단히 예를 들어보면, request가 500이고, limit이 1000 이라면, 컨테이너는 처음 시작될때 500을 할당 받고 실행되며, 많은 트래픽이 몰려 리소스가 부족하다면 최대 500만큼의 자원을 더 받을 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1595162054168&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-service
spec:
  replicas: 4
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 2 //팟을 시작&amp;amp;종료할때 2개씩 작업한다.
      maxUnavailable: 0 //롤링 업뎃시 모든 팟(4개)이 서비스 가능하도록. 
                          만약 1이면, 리플리카 4개중 1개는 작업불능
  template:
    spec:
      containers:
      - name: web-service
        resources:
          requests:
            cpu: 2000m
            memory: 2Gi
          limits:
            cpu: 4000m
            memory: 4Gi&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;Request&amp;amp;Limit을 지정해야하는 이유는? Overcommitted 상태&lt;/b&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이 &amp;nbsp;request와 limit의 개념이 있기 때문에 생기는 문제인데, request 된 양에 따라서 컨테이너를 만들었다고 하더라도, 컨테이너가 운영이되다가 자원이 모자르면 limit 에 정의된 양까지 계속해서 리소스를 요청하게 된다.&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 컨테이너의 총 Limit의 양이 실제 시스템이 가용한 resource의 양보다 많을 수 있는 경우가 발생한다. 이를 overcommitted 상태라고 한다. &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Overcommitted 상태가 발생하면, CPU의 경우에는 실제 사용량을 requested 에 정의된 상태까지 낮춘다. 예를 들어 limit이 500, request가 100인 경우, 현재 500으로 가동되고 있는 컨테이너의 CPU할당량을 100으로 낮춘다. 그래도 Overcommitted 상태가 해결되지 않는 경우, 우선 순위에 따라서 운영중인 컨테이너를 강제 종료 시킨다. &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;메모리의 경우에는 할당되어 사용중인 메모리의 크기를 줄일 수 는 없기 때문에, 우선 순위에 따라서 운영 중인 컨테이너를 강제 종료 시킨다. &amp;nbsp;Deployment,RS/RC에 의해 관리되고 있는 컨테이너는 다시 리스타트가 되고 초기 requested 상태의 만큼만 자원 (메모리/CPU)를 요청해서 사용하기 때문에, overcommitted &amp;nbsp;상태가 해제된다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Best practice&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;span style=&quot;color: #000000;&quot;&gt;구글 &lt;/span&gt;&lt;a href=&quot;https://cloud.google.com/blog/products/gcp/kubernetes-best-practices-resource-requests-and-limits&quot;&gt;&lt;span&gt;문서&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot;color: #000000;&quot;&gt;에 따르면 데이타 베이스등 아주 무거운 애플리케이션이 아니면, 일반적인 경우에는 CPU request를 100m 이하로 사용하기를 권장한다. &lt;/span&gt;&lt;span style=&quot;color: #000000; letter-spacing: 0px;&quot;&gt;또한 세밀하게 클러스터를 운영하기 어려운 경우에는 request와 limit의 사이즈를 같게 하는 것을 권장한다. limit이 request보다 클 경우 overcommitted 상태가 발생할 수 있는데, 이때 CPU가 throttle down 되면, 실제 필요한 CPU양 보다 작은 CPU양으로 줄어들기 때문에 성능저하가 발생할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&amp;lt;참조&amp;gt;&lt;/p&gt;
&lt;figure id=&quot;og_1595162413320&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;article&quot; data-og-title=&quot;쿠버네티스 #21 - 리소스(CPU/Memory) 할당과 관리&quot; data-og-description=&quot;쿠버네티스 리소스(CPU/Memory)할당과 관리 조대협 리소스 관리 쿠버네티스에서 Pod를 어느 노드에 배포할지를 결정하는 것을 스케쥴링이라고 한다. Pod에 대한 스케쥴링시에, Pod내의 애플리케이션�&quot; data-og-host=&quot;bcho.tistory.com&quot; data-og-source-url=&quot;https://bcho.tistory.com/1291&quot; data-og-url=&quot;https://bcho.tistory.com/1291&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dPoboE/hyGPfH7QN2/hrGwB8GxxMDJoi0XSESAlk/img.png?width=324&amp;amp;height=694&amp;amp;face=0_0_324_694,https://scrap.kakaocdn.net/dn/cMGc92/hyGPeh8o0o/I7FXq4gDVvKz1aZ7RG11GK/img.png?width=324&amp;amp;height=694&amp;amp;face=0_0_324_694,https://scrap.kakaocdn.net/dn/bR6eMC/hyGPdjfgpQ/ed4NVRMnVija4dd7lGkr41/img.png?width=1318&amp;amp;height=424&amp;amp;face=0_0_1318_424&quot;&gt;&lt;a href=&quot;https://bcho.tistory.com/1291&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://bcho.tistory.com/1291&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dPoboE/hyGPfH7QN2/hrGwB8GxxMDJoi0XSESAlk/img.png?width=324&amp;amp;height=694&amp;amp;face=0_0_324_694,https://scrap.kakaocdn.net/dn/cMGc92/hyGPeh8o0o/I7FXq4gDVvKz1aZ7RG11GK/img.png?width=324&amp;amp;height=694&amp;amp;face=0_0_324_694,https://scrap.kakaocdn.net/dn/bR6eMC/hyGPdjfgpQ/ed4NVRMnVija4dd7lGkr41/img.png?width=1318&amp;amp;height=424&amp;amp;face=0_0_1318_424');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;쿠버네티스 #21 - 리소스(CPU/Memory) 할당과 관리&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;쿠버네티스 리소스(CPU/Memory)할당과 관리 조대협 리소스 관리 쿠버네티스에서 Pod를 어느 노드에 배포할지를 결정하는 것을 스케쥴링이라고 한다. Pod에 대한 스케쥴링시에, Pod내의 애플리케이션�&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;bcho.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>인프라/Docker&amp;amp;Kubernetes</category>
      <category>CPU</category>
      <category>Kubernetes</category>
      <category>Memory</category>
      <category>resources</category>
      <category>도커</category>
      <category>리소스</category>
      <category>쿠버네티스</category>
      <author>여성게</author>
      <guid isPermaLink="true">https://coding-start.tistory.com/369</guid>
      <comments>https://coding-start.tistory.com/369#entry369comment</comments>
      <pubDate>Sun, 19 Jul 2020 21:41:38 +0900</pubDate>
    </item>
    <item>
      <title>Docker - 도커 이미지 만들기 ! (Dockerfile)</title>
      <link>https://coding-start.tistory.com/368</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lzwoY/btqFM9j269Q/KvkHCxn8vxNp1EuEFpuSc0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lzwoY/btqFM9j269Q/KvkHCxn8vxNp1EuEFpuSc0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lzwoY/btqFM9j269Q/KvkHCxn8vxNp1EuEFpuSc0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlzwoY%2FbtqFM9j269Q%2FKvkHCxn8vxNp1EuEFpuSc0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;오늘 다루어볼 포스팅은 &quot;도커 이미지 만들기&quot;이다. 이전에 한번 정리해야지해야지 하면서 미뤄왔었는데, 간단하게 다루어 볼것이다. 필자도 대충은 알았지, 뭔가 깊게 이해하지 못하고 이미지를 빌드했었는데, 이참에 기초부터 한번 정리해봐야겠다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;i&gt;&amp;lt;Dockerfile 작성을 위한 인스트럭션&amp;gt;&lt;/i&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;i&gt;1. FROM : 도커 이미지의 바탕이 될 베이스 이미지를 지정한다. Dockerfile로 이미지를 빌드할 때 먼저 FROM 인스트럭션에 지정된 이미지를 내려받는다. FROM에서 받아오는 도커 이미지는 도커 허브(Docker Hub)라는 레지스트리를 참조한다. 도커 특정 버전 이상에서는 Multi stage build가 가능해져서, 하나의 베이스 이미지(FROM ..)가 아닌 여러 베이스 이미지를 사용하여 빌드가 가능하다(FROM을 여러번 사용)&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;i&gt;2. RUN : 도커 이미지를 실행할 때 컨테이너 안에서 실행할 명령을 정의하는 인스트럭션이다. 인자로 도커 컨테이너 안에서 실행할 명령을 그대로 기술한다.&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;i&gt;3. COPY : 도커가 동작 중인 호스트 머신의 파일이나 디렉터리를 도커 컨테이너 안으로 복사하는 인스트럭션이다.&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;i&gt;4. CMD : 도커 컨테이너를 실행할 때 컨테이너 안에서 실행할 프로세스를 지정한다. 2번의 RUN 인스트럭션은 이미지를 빌드할 때 실행되고, CMD는 컨테이너를 시작할 때 한 번 실행된다.&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1595153656674&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&quot;&amp;gt; go run  /echo/main.go&quot;를 CMD 인스트럭션에 기술하면 아래와 같다.
CMD [&quot;go&quot;, &quot;run&quot;, &quot;/echo/main.go&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;i&gt;5. ENTRYPOINT : CMD와 마찬가지로 컨테이너 안에서 실행할 프로세스를 지정하는 인스트럭션이다. ENTRYPOINT를 지정하면 CMD의 인자가 ENTRYPOINT에서 실행하는 파일(셸 등)에 인자로 전달된다. 즉, ENTRYPOINT에 지정된 값이 기본 프로세스를 지정하는 것이다.&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1595154430033&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;FROM golang:1.10

#./entry.sh을 실행시키면서 ARG1, ARG2를 entry.sh의 인자로 전달한다.
ENTRYPOINT [&quot;./entry.sh&quot;]
CMD [&quot;ARG1&quot;, &quot;ARG2&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;i&gt;6. LABEL : 이미지를 만든 사람의 이름 등을 적을 수 있다.&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;i&gt;7. ENV : 도커 컨테이너 안에서 사용할 수 있는 환경변수를 지정한다.&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;i&gt;8. ARG : 이미지를 빌드할 때 정보를 함께 넣기 위해 사용한다. 이미지를 빌드할 때만 사용할 수 있는 일시적인 환경변수다.&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>인프라/Docker&amp;amp;Kubernetes</category>
      <category>container</category>
      <category>docker</category>
      <category>Dockerfile</category>
      <category>image</category>
      <category>keburnetes</category>
      <category>도커</category>
      <category>도커이미지</category>
      <category>컨테이너</category>
      <category>쿠버네티스</category>
      <author>여성게</author>
      <guid isPermaLink="true">https://coding-start.tistory.com/368</guid>
      <comments>https://coding-start.tistory.com/368#entry368comment</comments>
      <pubDate>Sat, 18 Jul 2020 23:12:21 +0900</pubDate>
    </item>
    <item>
      <title>gRPC - convert proto generate java to jsonString</title>
      <link>https://coding-start.tistory.com/367</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cebEAF/btqFHqk6wat/LUCDzptvyEhy7hvKaHUZQK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cebEAF/btqFHqk6wat/LUCDzptvyEhy7hvKaHUZQK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cebEAF/btqFHqk6wat/LUCDzptvyEhy7hvKaHUZQK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcebEAF%2FbtqFHqk6wat%2FLUCDzptvyEhy7hvKaHUZQK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이번 시간에 다루어볼 내용은 proto message로 생성한 Java를 Json String으로 변환하는 방법이다. proto로 생성한 java 인스턴스를 아래와 같이 json string으로 바꾸려면 예외가 발생한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1594812427363&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#protoJava - proto로 생성한 java instance
ObjectMapper mapper = new ObjectMapper();
mapper.writeValueAsString(protoJava);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그렇기 때문에 protobuf의 JsonFormat으로 jsonString을 변환해주면 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1594812337358&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#protoJava - proto file로 생성한 java instance
final String jsonString = JsonFormat.printer().print(protoJava);&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Web/gRPC</category>
      <category>google</category>
      <category>gRPC</category>
      <category>json</category>
      <category>proto</category>
      <category>proto3</category>
      <category>구글</category>
      <category>프로토</category>
      <author>여성게</author>
      <guid isPermaLink="true">https://coding-start.tistory.com/367</guid>
      <comments>https://coding-start.tistory.com/367#entry367comment</comments>
      <pubDate>Wed, 15 Jul 2020 20:27:42 +0900</pubDate>
    </item>
    <item>
      <title>Spring Data - MongoDB&amp;amp;queryDsl 예제</title>
      <link>https://coding-start.tistory.com/365</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LLrPN/btqE83caka9/8Dv6XDh3JY4jSDmd2slik0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LLrPN/btqE83caka9/8Dv6XDh3JY4jSDmd2slik0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LLrPN/btqE83caka9/8Dv6XDh3JY4jSDmd2slik0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLLrPN%2FbtqE83caka9%2F8Dv6XDh3JY4jSDmd2slik0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;오늘 다루어볼 내용은 spring data mongo + querydsl 연동 및 간단한 예제를 다루어볼 것이다. 예제 환경은 아래와 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1593262837815&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;- gradle : 6.4.1
- spring boot : 2.3.1.RELEASE&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;모든 코드는 아래 깃헙을 참고하자.&lt;/p&gt;
&lt;figure id=&quot;og_1593269403139&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;object&quot; data-og-title=&quot;yoonyeoseong/spring-mongo-querydsl&quot; data-og-description=&quot;Contribute to yoonyeoseong/spring-mongo-querydsl development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/yoonyeoseong/spring-mongo-querydsl&quot; data-og-url=&quot;https://github.com/yoonyeoseong/spring-mongo-querydsl&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/by87fV/hyGzdqniV2/nbplWWtokcveQd0EAwqdV0/img.jpg?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400&quot;&gt;&lt;a href=&quot;https://github.com/yoonyeoseong/spring-mongo-querydsl&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/yoonyeoseong/spring-mongo-querydsl&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/by87fV/hyGzdqniV2/nbplWWtokcveQd0EAwqdV0/img.jpg?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;yoonyeoseong/spring-mongo-querydsl&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;Contribute to yoonyeoseong/spring-mongo-querydsl development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;lt;gradle 설정&amp;gt;&lt;/p&gt;
&lt;p&gt;아래는 spring data mongo와 querydsl 연동을 위한 gradle 설정이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;colorscripter-code&quot; style=&quot;color: #010101; font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace !important; position: relative !important; overflow: auto;&quot;&gt;
&lt;table class=&quot;colorscripter-code-table&quot; style=&quot;margin: 0; padding: 0; border: none; background-color: #fafafa; border-radius: 4px;&quot; cellspacing=&quot;0&quot; cellpadding=&quot;0&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;padding: 6px; border-right: 2px solid #e5e5e5;&quot;&gt;
&lt;div style=&quot;margin: 0; padding: 0; word-break: normal; text-align: right; color: #666; font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace !important; line-height: 130%;&quot;&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;1&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;2&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;3&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;4&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;5&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;6&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;7&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;8&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;9&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;10&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;11&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;12&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;13&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;14&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;15&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;16&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;17&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;18&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;19&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;20&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;21&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;22&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;23&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;24&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;25&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;26&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;27&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;28&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;29&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;30&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;31&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;32&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;33&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;34&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;35&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;36&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;37&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;38&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;39&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;40&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;41&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;42&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;43&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;44&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;45&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;46&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;47&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;48&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;49&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;50&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;51&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;52&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;53&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;54&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;55&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;56&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;57&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;58&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;59&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;60&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;61&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;62&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;63&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;64&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;65&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;66&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;67&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;68&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;69&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;70&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;71&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;72&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;73&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;74&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;75&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;76&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;77&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;78&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;79&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;80&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;81&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;82&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;83&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;84&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;85&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;86&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;87&lt;/div&gt;
&lt;div style=&quot;line-height: 130%;&quot;&gt;88&lt;/div&gt;
&lt;/div&gt;
&lt;/td&gt;
&lt;td style=&quot;padding: 6px 0; text-align: left;&quot;&gt;
&lt;div style=&quot;margin: 0; padding: 0; color: #010101; font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace !important; line-height: 130%;&quot;&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;buildscript&amp;nbsp;{&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ext&amp;nbsp;{&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;queryDslVersion&amp;nbsp;=&amp;nbsp;'4.3.0'&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;repositories&amp;nbsp;{&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;maven&amp;nbsp;{&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;url&amp;nbsp;&quot;https://plugins.gradle.org/m2/&quot;&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;dependencies&amp;nbsp;{&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;classpath(&quot;org.springframework.boot:spring-boot-gradle-plugin:2.3.1.RELEASE&quot;)&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;}&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;plugins&amp;nbsp;{&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;id&amp;nbsp;'org.springframework.boot'&amp;nbsp;version&amp;nbsp;'2.3.1.RELEASE'&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;id&amp;nbsp;'io.spring.dependency-management'&amp;nbsp;version&amp;nbsp;'1.0.9.RELEASE'&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;id&amp;nbsp;&quot;com.ewerk.gradle.plugins.querydsl&quot;&amp;nbsp;version&amp;nbsp;'1.0.10'&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;id&amp;nbsp;'java'&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;id&amp;nbsp;'idea'&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;}&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;group&amp;nbsp;=&amp;nbsp;'com.levi'&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;version&amp;nbsp;=&amp;nbsp;'0.0.1-SNAPSHOT'&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;sourceCompatibility&amp;nbsp;=&amp;nbsp;'14'&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;repositories&amp;nbsp;{&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;mavenCentral()&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;}&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;dependencies&amp;nbsp;{&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;compile&amp;nbsp;'org.springframework.boot:spring-boot-starter-data-mongodb-reactive'&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;compile&amp;nbsp;'org.springframework.boot:spring-boot-starter-webflux'&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;compile('org.springframework.boot:spring-boot-configuration-processor')&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;compile&amp;nbsp;&quot;com.querydsl:querydsl-mongodb:${queryDslVersion}&quot;&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;compile&amp;nbsp;'org.projectlombok:lombok'&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;annotationProcessor(&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;'org.springframework.boot:spring-boot-configuration-processor',&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;com.querydsl:querydsl-apt:${queryDslVersion}&quot;,&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;'org.projectlombok:lombok'&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;testImplementation('org.springframework.boot:spring-boot-starter-test')&amp;nbsp;{&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;exclude&amp;nbsp;group:&amp;nbsp;'org.junit.vintage',&amp;nbsp;module:&amp;nbsp;'junit-vintage-engine'&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;testImplementation&amp;nbsp;'io.projectreactor:reactor-test'&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;}&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;test&amp;nbsp;{&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;useJUnitPlatform()&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;}&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;def&amp;nbsp;querydslSrcDir&amp;nbsp;=&amp;nbsp;'src/main/querydsl'&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;querydsl&amp;nbsp;{&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;springDataMongo&amp;nbsp;=&amp;nbsp;true&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;querydslSourcesDir&amp;nbsp;=&amp;nbsp;querydslSrcDir&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;}&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;sourceSets&amp;nbsp;{&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;main&amp;nbsp;{&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;java&amp;nbsp;{&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;srcDirs&amp;nbsp;=&amp;nbsp;['src/main/java',&amp;nbsp;querydslSrcDir]&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;}&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;compileQuerydsl{&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;options.annotationProcessorPath&amp;nbsp;=&amp;nbsp;configurations.querydsl&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;}&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;configurations&amp;nbsp;{&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;compileOnly&amp;nbsp;{&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;extendsFrom&amp;nbsp;annotationProcessor&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;querydsl.extendsFrom&amp;nbsp;compileClasspath&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;}&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;project.afterEvaluate&amp;nbsp;{&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;project.tasks.compileQuerydsl.options.compilerArgs&amp;nbsp;=&amp;nbsp;[&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;-proc:only&quot;,&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;-processor&quot;,&amp;nbsp;project.querydsl.processors()&amp;nbsp;+&amp;nbsp;',lombok.launch.AnnotationProcessorHider$AnnotationProcessor'&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;]&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;}&lt;/div&gt;
&lt;div style=&quot;padding: 0 6px; white-space: pre; line-height: 130%;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div style=&quot;text-align: right; margin-top: -13px; margin-right: 5px; font-size: 9px; font-style: italic;&quot;&gt;&lt;a style=&quot;color: #e5e5e5text-decoration:none;&quot; href=&quot;http://colorscripter.com/info#e&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Colored by Color Scripter&lt;/a&gt;&lt;/div&gt;
&lt;/td&gt;
&lt;td style=&quot;vertical-align: bottom; padding: 0 2px 4px 0;&quot;&gt;&lt;a style=&quot;text-decoration: none; color: white;&quot; href=&quot;http://colorscripter.com/info#e&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span style=&quot;font-size: 9px; word-break: normal; background-color: #e5e5e5; color: white; border-radius: 10px; padding: 1px;&quot;&gt;cs&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;작성중...&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;lt;참고&amp;gt;&lt;/p&gt;
&lt;figure id=&quot;og_1593068559076&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Spring Data MongoDB - Reference Documentation&quot; data-og-description=&quot;As of version 3.6, MongoDB supports the concept of sessions. The use of sessions enables MongoDB&amp;rsquo;s Causal Consistency model, which guarantees running operations in an order that respects their causal relationships. Those are split into ServerSession inst&quot; data-og-host=&quot;docs.spring.io&quot; data-og-source-url=&quot;https://docs.spring.io/spring-data/mongodb/docs/current/reference/html/#new-features.2-2-0&quot; data-og-url=&quot;https://docs.spring.io/spring-data/mongodb/docs/current/reference/html/#new-features.2-2-0&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/sKLOC/hyGyehTIUU/WKw5n5Hzwm5JL010vHC9nK/img.png?width=900&amp;amp;height=529&amp;amp;face=0_0_900_529&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-data/mongodb/docs/current/reference/html/#new-features.2-2-0&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.spring.io/spring-data/mongodb/docs/current/reference/html/#new-features.2-2-0&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/sKLOC/hyGyehTIUU/WKw5n5Hzwm5JL010vHC9nK/img.png?width=900&amp;amp;height=529&amp;amp;face=0_0_900_529');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;Spring Data MongoDB - Reference Documentation&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;As of version 3.6, MongoDB supports the concept of sessions. The use of sessions enables MongoDB&amp;rsquo;s Causal Consistency model, which guarantees running operations in an order that respects their causal relationships. Those are split into ServerSession inst&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;docs.spring.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;figure id=&quot;og_1593069018950&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;blog&quot; data-og-title=&quot;gradle 프로젝트에서 querydsl 설정하기&quot; data-og-description=&quot;gradle 4.6 / querydsl 4.2.1 / spring-data-jpa 1.11.13.RELEASE / spring-data-mongodb 1.10.8.RELEASE이 환경을 어떻게 gradle 설정으로 푸는지 정리힙니다.&quot; data-og-host=&quot;mingpd.github.io&quot; data-og-source-url=&quot;https://mingpd.github.io/2019/05/20/develop/gradle-querydsl/&quot; data-og-url=&quot;https://mingpd.github.io/2019/05/20/develop/gradle-querydsl/index.html&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/I2k0G/hyGw8jipl8/j7HMHdeRSOwkVYn9ImBkSK/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400,https://scrap.kakaocdn.net/dn/KWDLT/hyGw3bd4KF/wHE6CYNCekLduZ9P9KE091/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400&quot;&gt;&lt;a href=&quot;https://mingpd.github.io/2019/05/20/develop/gradle-querydsl/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://mingpd.github.io/2019/05/20/develop/gradle-querydsl/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/I2k0G/hyGw8jipl8/j7HMHdeRSOwkVYn9ImBkSK/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400,https://scrap.kakaocdn.net/dn/KWDLT/hyGw3bd4KF/wHE6CYNCekLduZ9P9KE091/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;gradle 프로젝트에서 querydsl 설정하기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;gradle 4.6 / querydsl 4.2.1 / spring-data-jpa 1.11.13.RELEASE / spring-data-mongodb 1.10.8.RELEASE이 환경을 어떻게 gradle 설정으로 푸는지 정리힙니다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;mingpd.github.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;figure id=&quot;og_1593154846571&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;object&quot; data-og-title=&quot;ewerk/gradle-plugins&quot; data-og-description=&quot;A collection of Gradle plugins. Contribute to ewerk/gradle-plugins development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/ewerk/gradle-plugins/tree/master/querydsl-plugin&quot; data-og-url=&quot;https://github.com/ewerk/gradle-plugins&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/ciK5rx/hyGy46TuzL/7u2Wav9oZOQVzScegdaCJk/img.jpg?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400&quot;&gt;&lt;a href=&quot;https://github.com/ewerk/gradle-plugins/tree/master/querydsl-plugin&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/ewerk/gradle-plugins/tree/master/querydsl-plugin&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/ciK5rx/hyGy46TuzL/7u2Wav9oZOQVzScegdaCJk/img.jpg?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;ewerk/gradle-plugins&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;A collection of Gradle plugins. Contribute to ewerk/gradle-plugins development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;</description>
      <category>Web/Spring</category>
      <author>여성게</author>
      <guid isPermaLink="true">https://coding-start.tistory.com/365</guid>
      <comments>https://coding-start.tistory.com/365#entry365comment</comments>
      <pubDate>Thu, 25 Jun 2020 16:03:14 +0900</pubDate>
    </item>
  </channel>
</rss>