반응형

List 정렬하는법

//기본 오름차순
Collections.sort(list);

//내림차순
Collections.sort(names, Collections.reverseOrder());

//person 객체의 age로 정렬하기
Collections.sort(list,Comparator.comparingInt(person -> person.age))

//문자열의 길이를 기준으로 정렬
Collections.sort(names, Comparator.comparingInt(String::length));

 

import java.util.*;

class Solution {
    public int[][] solution(int[][] data, String ext, int val_ext, String sort_by) {
        
        HashMap<String,Integer> columnInfo= new HashMap<>();
        columnInfo.put("code",0);
        columnInfo.put("date",1);
        columnInfo.put("maximum",2);
        columnInfo.put("remain",3);
        
        
        //조건 인덱스
        int extIndex = columnInfo.get(ext);
        //정렬 인덱스
        int sortIndex = columnInfo.get(sort_by);
        
        List<int[]> newData = new ArrayList<>();
        for(int i=0;i<data.length;i++){
            int[] detailData = data[i];
            //조건부합하는지 검사
            if(detailData[extIndex] < val_ext){
                newData.add(detailData);
            }           
        }
        
        //List 정렬
        Collections.sort(newData,Comparator.comparingInt(list->list[sortIndex]));
        //Collections.sort(newData,(a,b)->Integer.compare(a[sortIndex],b[sortIndex]));
       
        

        
        int[][] answer = new int[newData.size()][4];
        answer = newData.toArray(new int[0][]);
     
        return answer;
          
    }
}
반응형
반응형

배열 정렬

Arrays.sort();

배열 비교

Arrays.mismatch();

import java.util.*;

class Solution {
    public String solution(String[] participant, String[] completion) {
        Arrays.sort(participant);
        Arrays.sort(completion);
        int mismatchIndex = Arrays.mismatch(participant,completion);
        return participant[mismatchIndex];
        
    }
}

 

다른방법

맵의 key-value에 접근하는법

Set<Map.Entry<String, Integer>> entrySet = map.entrySet();

 

맵의 모든 key 가져오기

 Set<String> keys = map.keySet();

 

맵의 모든 value 가져오기

Collection<String> values = map.values();

 

 

import java.util.*;

class Solution {
    public String solution(String[] participant, String[] completion) {
        HashMap<String,Integer> particimap = new HashMap<>();
        for(String name : participant){
            int cnt = particimap.getOrDefault(name,0);
            particimap.put(name,++cnt);
        }
        
        for(String name : completion){
            int cnt = particimap.get(name);
            particimap.put(name,--cnt);
        }
        
        return particimap.entrySet()
            .stream()
            .filter(entry -> entry.getValue() >0)
            .map(Map.Entry::getKey)
            .findFirst()
            .orElse("없음");
    }
}

 

 

반응형

'코딩 관련 > 코딩문제풀기' 카테고리의 다른 글

[프로그래머스] 키패드 누르기  (0) 2024.10.09
[프로그래머스] 문자열 나누기  (0) 2024.10.06
[프로그래머스] 숫자 짝꿍  (0) 2024.10.04
[JAVA] 9012번 괄호  (0) 2023.03.13
[JAVA] 너의 평점은  (0) 2023.03.12
반응형

개선사항

1. 두 정수 비교시 for문 보다는 Math.min() 사용하기

2. String 에서 문자 하나 추출시 substring(i,i-1) 보다는 charAt() 사용하기

3. char 을 정수형으로 변환

//1.'0'빼기. '0'의 ASCII값 : 48
int text1 = '1';
int num1 = text1 - '0';

//2. Character.getNumericValue() 
int text2 = '1';
int num2 = Character.getNumericValue(text2);
반응형

'코딩 관련 > 코딩문제풀기' 카테고리의 다른 글

[프로그래머스] 문자열 나누기  (0) 2024.10.06
[프로그래머스] 완주하지 못한 선수  (0) 2024.10.04
[JAVA] 9012번 괄호  (0) 2023.03.13
[JAVA] 너의 평점은  (0) 2023.03.12
[JAVA] 그룹 단어 체커  (0) 2023.03.12
반응형

N+1 이란?

예를 들어 Stuedent 엔티티에 GradeInfo엔티티와 ClassInfo엔티티가 EAGER Loading으로 연관매핑된 경우

@Comment("교실정보")
@ManyToOne //디폴트 값은 (fetch = FetchType.EAGER)
@JoinColumn(name = "class", referencedColumnName = "classId")
private ClassInfo class;


@Comment("학년정보")
@ManyToOne
@JoinColumn(name = "grade", referencedColumnName = "gradeId")
private GradeInfo grade;

아래와 같은 JPA 쿼리메소드를 사용할 때

studentRepository.findAll();

findAll() 수행 시점에 Student 엔티티를 조회하는 select 쿼리와

+ 매핑된 GradeInfo 엔티티를 조회하는 select 쿼리,

+ 매핑된 ClassInfo 엔티티를 조회하는 select 쿼리,

모두 수행된다.

예를 들어 1000개의 Student 엔티티는 각각 1,2,3,4,5,6 이라는 6개 중 하나의 gradeInfo 값을 가질 수 있고

1-1, 1-2, 1-3, 1-4, 1-5, 1-6 부터 6학년 6-6 까지 이라는 총 36개중 하나의 ClassInfo 값을 가질 수 있다하면

전체 Student 를 조회할 시 student에 연관된 GradeInfo 엔티티를 조회하기 위해 총 6번의 select,

ClassInfo 엔티티를 조회하기 위한 36번의 select가 추가로 나감. 이것을 N+1 문제라고 함

 

왜 join으로 쿼리 생성이 되지 않고 select가 각각 나가는걸까

=> JPA가 메소드 이름을 분석해서 JPQL을 생성하고 실행함.
=> JPQL을 생성할때는 fetch 전략을 참고하지 않기 때문

 

LAZY Loading 전략을 사용하면 N+1문제가 해결되나?

=> 해당 엔티티에 연관매핑된 엔티티를 조회(사용)할 때 select가 추가로 나가게 됨. 

=> 또는 엔티티 return 시에 결국 select하게 됨

=> 해결 안 됨

 

QueryDSL을 사용해도 N+1 문제가 생기나

QueryDSL은 JPQL 빌더역할을 해주는 것이기 때문에 QueryDSL 로 entity select하는 경우에도 발생함.

leftJoin을 걸어놨지만 join이 되지않고 select가 각각 나간다.

public List<Goods> nplus1test(){
        List<Student> result = queryFactory
                    .select(qStudent) 
                    .from(qStudent)
                    .leftJoin(qStudent.grade,qGradeInfo)
                    .leftJoin(qStudent.class,qClassInfo)  
                    .fetch();

 

entity 전체가 아닌 컬럼을 지정해서 뽑는다면 해결되나

컬럼 지정시 join이 되지만 컬럼을 지정해서 뽑으면 tuple 로 반환되어  불편함

public List<Tuple> nplus1test(){
  List<Tuple> result = queryFactory
                                  .select(qStudent.name,qStudent.grade)
                                  .from(qStudent)
                                  .fetch();
  for(Tuple tuple : result){
          System.out.println(tuple.get(qStudent.name));
          System.out.println(tuple.get(qStudent.grade));
  }
  return result;
}

=> Projections.bean 혹은 Projections.fields를 사용해 DTO를 반환받으면 됨

 

그래도 entity 전체를 select하고 싶다면

EntityGraph를 사용하면 됨

EntityGraph : DataJPA에서 fetchjoin을 어노테이션으로 사용할 수 있도록 하였다. 연관관계가 지연로딩으로 되어있는 엔티티를 조회할 경우 fetch join을 사용한다. 

fetchjoin : select 대상 엔티티와 fetch join이 걸려있는 엔티티를 포함해 join하여 select 함. 

EntityGraph 사용 예시

@EntityGraph(attributePaths = {"class","grade"})
List<Student> findAll();

 

fetchjoin 사용 예시

@Override
public List<Goods> nplus1test(){
        List<Goods> result = queryFactory
                    .select(qStudent)
                    .from(qStudent)
                    .leftJoin(qStudent.class,qClassInfo)
                    .fetchJoin()  
                    .leftJoin(qStudent.grade,qGradeInfo)
                    .fetchJoin()    
                    .fetch();
        return result;
}
반응형
반응형

"Must create a new ApplePaySession from a user gesture handler"라는 에러는 Apple의 보안 정책 중 하나입니다. 이 에러는 사용자 동작(user gesture) 없이 Apple Pay 세션을 시작하려고 할 때 발생할 수 있습니다. Apple은 사용자의 명시적인 동의 없이 결제를 진행하거나 개인 정보를 요청하는 것을 방지하기 위해 이러한 정책을 시행합니다.

따라서 Apple Pay 세션을 시작하는 코드는 사용자의 명시적인 동작에 반응하여 실행되어야 합니다. 사용자가 버튼을 클릭하거나 터치할 때 세션을 시작하도록 구현해야 합니다. 사용자 동작 이벤트 핸들러 내부에서 Apple Pay 세션을 시작하도록 코드를 변경해야 합니다.

다시 말씀드리면, Apple Pay 세션을 시작하는 함수는 사용자가 버튼을 클릭하거나 터치하는 등의 동작에 반응하여 호출되어야 합니다. 사용자 동작과 관련된 이벤트 핸들러 내에서 해당 함수를 호출하십시오. 이렇게 하면 보안 정책을 준수하면서도 사용자가 Apple Pay를 시작하도록 허용됩니다.

반응형
반응형

1. kafka client 다운로드 

https://kafka.apache.org/downloads

Binary 다운로드
tgz파일 압축 풀어서 C에 갖다놓기
(실행시 path가 길면 "입력 줄이 너무 깁니다." 가 뜨기 때문)

 

2. 클러스터에 연결하기 위해 IAM 인증을 사용할 것이므로 IAM 인증용 jar 다운로드

https://github.com/aws/aws-msk-iam-auth/releases
jar 파일을 libs 폴더 밑에 넣는다.(C에 위치한 경우 C:\kafka_2.13-3.6.1\libs 의 밑 ) 

 

 

3. config 폴더 밑에 client.properties 파일 생성하여 아래 내용 입력

security.protocol=SASL_SSL
sasl.mechanism=AWS_MSK_IAM
sasl.jaas.config=software.amazon.msk.auth.iam.IAMLoginModule required;
sasl.client.callback.handler.class=software.amazon.msk.auth.iam.IAMClientCallbackHandler

 

cmd에서 bin\windows로 가서 명령어 입력하면 됨

 

토픽 리스트 조회
kafka-topics.bat --list --bootstrap-server [브로커엔드포인주소]:[포트] --command-config C:\kafka_2.13-3.6.1\config\client.properties

토픽 삭제
kafka-topics.bat --bootstrap-server [브로커엔드포인주소]:[포트] --delete --topic [토픽이름] --command-config C:\kafka_2.13-3.6.1\config\client.properties

토픽 생성
kafka-topics.bat --bootstrap-server [브로커엔드포인주소]:[포트] --create --topic [토픽이름] --partitions [개수] --replication-factor [개수] --command-config C:\kafka_2.13-3.6.1\config\client.properties

토픽 구성정보 조회
kafka-topics.bat --topic [토픽이름] --describe --bootstrap-server [브로커엔드포인주소]:[포트] --command-config C:\kafka_2.13-3.6.1\config\client.properties

토픽의 record 조회
kafka-console-consumer.bat --bootstrap-server [브로커엔드포인주소]:[포트] --topic [토픽이름] --from-beginning --consumer.config C:\kafka_2.13-3.6.1\config\client.properties --property print.offset=true --property print.timestamp=true --property print.key=true

토픽에 메세지 발행
kafka-console-producer.bat --topic order-status --bootstrap-server b-2.mskdominos.ormjxv.c2.kafka.ap-northeast-2.amazonaws.com:9098 --producer.config C:\kafka_2.13-3.6.1\config\client.properties

클러스터에 연결된 컨슈머 그룹 리스트 조회
kafka-consumer-groups.bat --bootstrap-server [브로커엔드포인주소]:[포트] --command-config C:\kafka_2.13-3.6.1\config\client.properties --list
amazon.msk.canary.group.broker-1

컨슈머 그룹 오프셋 정보 조회
kafka-consumer-groups.bat --bootstrap-server [브로커엔드포인주소]:[포트] --command-config C:\kafka_2.13-3.6.1\config\client.properties --describe --group [그룹이름] --offsets

반응형
반응형

스프링부트에서 STOMP를 이용한 메세지 pub/sub 예시

build.gradle에서 라이브러리를 추가해주십쇼 

implementation 'org.springframework.boot:spring-boot-starter-websocket'

 

웹소켓 설정파일 WebsocketConfig.java 을 아래와 같은 내용으로 생성

import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
import lombok.RequiredArgsConstructor;

@Configuration
@EnableWebSocketMessageBroker
@RequiredArgsConstructor
public class WebsocketConfig implements WebSocketMessageBrokerConfigurer {

@Override
    public void configureMessageBroker(MessageBrokerRegistry registry){
        // /pub으로 시작하는 요청은 @Controller의 @MessageMapping 메소드로 라우트됨
        // 클라이언트가 서버로 메세지를 보낼 때 붙여야하는 prefix
        registry.setApplicationDestinationPrefixes("/pub"); 

         //해당 문자열로 시작하는 message 주소값을 받아서 처리하는 Broker를 활성화한다.
        registry.enableSimpleBroker("/sub"); //메세지 구독 주소값
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry){
        registry.addEndpoint("/websocket")  //클라이언트가 연결할 url
        .setAllowedOriginPatterns("*")
        .withSockJS(); 
        //withSockJS : 웹소켓을 지원하지 않는 환경에서도 웹소켓 사용을 가능하게 해주는 옵션
        //이 옵션을 달면 클라이언트에서 SockJS 라이브러리를 사용해야 함
        //http 아니면 https 로 연결가능(sockJS가 http를 ws로 변환해줌)
    }
}

 

서버에서 STOMP 메세지 보내기

- convertAndSend 메소드를 호출하면 클라이언트가 구독하고 있는 주소로 메세지가 발행됨.

- /sub/** 주소를 구독하고 있는 클라이언트에게 메세지를 전달하는 예시 

import java.util.HashMap;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.stereotype.Component;
import lombok.RequiredArgsConstructor;


@Component
@RequiredArgsConstructor
public class sendService{
    private final SimpMessagingTemplate messagingTemplate;

    public void sendMessageToSubOrderTopic(String message,String id) {
        messagingTemplate.convertAndSend("/sub/"+id, message);
    }
}

 

 

 

서버에서 STOMP 메세지 받아서 전달하기

- 서버가 메세지 브로커 역할 수행

- 특정 클라이언트가 /sub/test 주소로 메세지를 보내면 @MessageMapping에서 받아 @SendTo에 설정된 쪽으로 전달

- /sub/** 을 구독하는 클라이언트가 메세지를 받아볼 수 있는 예시

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.handler.annotation.DestinationVariable;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Controller
public class StompMessageController {
/* 클라이언트에서 웹소켓을 연결한 뒤 /pub/test 로 메세지를 보내면
 여기서 받아 SendTo에 명시된 구독 링크로 메세지를 발행
 prefix는 생략
*/
    @MessageMapping("/test")
    @SendTo("/sub")
    public String processMessage(String message){
        System.out.println("message : " + message);
        return message;

    }

    //STOMP를 쓰면 좋은점
   	/* @Controller 적용된 객체를 이용해 조직적으로 관리할 수 있다.
    	STOMP의 Destination 경로를 기반으로 Spring Security를 적용할 수 있다
    	외부 브로커를 이용해 여러 서버를 관리할 수 있다.
    */


}

 


그럼 메세지를 구독하고 보내는 클라이언트 페이지를 띄워 테스트를 해보자..

검색해보면 전부 다 APIC으로 테스트를 하던데 APIC자체를 못찾아서 javascript로 짜서 했음. 

html 페이지에 javascript 소스를 넣는다.

 

메세지 구독하는 클라이언트 페이지 소스 예시 (javascript)

서버에 withSockJS() 설정이 되어있다면 SockJS 를 이용하여 연결해야 하며 이 경우엔 http나 https 로 접속을 해야함.

서버에서 /sub 주소로 구독하고 있는 클라이언트에 메세지를 전달해주므로 

/sub/aaa이나  /sub/bbb 를 구독하는 쪽은 메세지를 다 받을 수 있음. 

<script src="https://cdnjs.cloudflare.com/ajax/libs/sockjs-client/1.6.1/sockjs.min.js" integrity="sha512-1QvjE7BtotQjkq8PxLeF6P46gEpBRXuskzIVgjFpekzFVF4yjRgrQvTG1MTOJ3yQgvTteKAcO7DSZI92+u/yZw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/stomp.js/2.3.3/stomp.min.js"></script>

<script type="text/javascript">
var socket = new SockJS("http://localhost:8081/websocket");
stompClient = Stomp.over(socket);
stompClient.connect({}, function (frame) {
    console.log("연결 완료");
    stompClient.subscribe("/sub/aaa",function(response){
        console.log(response.body);
  });	
});
</script>

 

메세지를 발행하는 클라이언트 페이지 소스 예시(javascript)

<script src="https://cdnjs.cloudflare.com/ajax/libs/sockjs-client/1.6.1/sockjs.min.js" integrity="sha512-1QvjE7BtotQjkq8PxLeF6P46gEpBRXuskzIVgjFpekzFVF4yjRgrQvTG1MTOJ3yQgvTteKAcO7DSZI92+u/yZw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/stomp.js/2.3.3/stomp.min.js"></script>

<script type="text/javascript">
var socket = new SockJS("http://localhost:8081/websocket");
stompClient = Stomp.over(socket);
stompClient.connect({}, function (frame) {
    console.log("연결 완료");
    stompClient.send("/pub/test",{},"test메시지입니다.");
});
</script>

 

SockJS를 안 쓰고 싶다면

서버에서 해당 설정을 없애주고

클라이언트에서는 stomp 라이브러리로 바로 연결하면 댐

<script src="https://cdnjs.cloudflare.com/ajax/libs/sockjs-client/1.6.1/sockjs.min.js" integrity="sha512-1QvjE7BtotQjkq8PxLeF6P46gEpBRXuskzIVgjFpekzFVF4yjRgrQvTG1MTOJ3yQgvTteKAcO7DSZI92+u/yZw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/stomp.js/2.3.3/stomp.min.js"></script>
<script type="text/javascript">
var client = Stomp.client("ws://localhost:8081/websocket");
client.connect({},function(){
    console.log("연결 완료");
    client.subscribe("/sub/aaa",function(message){
        console.log("받은 메세지 : " + mesasge);
    });
})
	
</script>
반응형
반응형

1. websocket 연결설정

import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;

import lombok.RequiredArgsConstructor;

@Configuration
@EnableWebSocket
@RequiredArgsConstructor
public class WebSocketConfig implements WebSocketConfigurer{

    //밑에서 만들 WebSocketHandler 클래스
    private final WebSocketHandler webSocketHandler;

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry){
        registry.addHandler(webSocketHandler,"/websocket").setAllowedOrigins("*");
        // /websocket : 연결url
        //setAllowedOrigins : 웹소켓 cors정책으로, 허용 도메인 지정
        
    }

}

 

2. websocket 핸들러 생성. web socket 연결 및 종료의 수행에 대한 내용

import java.util.concurrent.ConcurrentHashMap;

import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

@Component
//TextWebSocketHandler 상속 시 3개의 메소드 오버라이딩
public class WebSocketHandler extends TextWebSocketHandler{
    
    //ConcurrentHashMap : 멀티 스레드 환경에서 사용. entry 아이템별로 락을 건다.
    private static final ConcurrentHashMap<String, WebSocketSession> CLIENTS = new ConcurrentHashMap<String, WebSocketSession>();
    
    public void afterConnectionEstablished(WebSocketSession session)throws Exception{
        CLIENTS.put(session.getId(), session);
        System.out.println("session Id(" + session.getId() + ") 연결");
        //출력예시 : session Id(84693265-e147-0b2c-5505-b2d3c62e62b4) 연결
    }

    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception{
        CLIENTS.remove(session.getId());
        System.out.println("session Id(" + session.getId() + ") 연결해제");
        //출력예시 : session Id(03017781-abf2-bc66-855e-f217bb99b275) 연결해제
    }

    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception{
        String id = session.getId();
        System.out.println(CLIENTS.toString());
        //출력예시 : {03017781-abf2-bc66-855e-f217bb99b275=StandardWebSocketSession[id=03017781-abf2-bc66-855e-f217bb99b275, uri=ws://localhost:8081/websocket]}
        CLIENTS.entrySet().forEach(arg ->{
            if(!arg.getKey().equals(id)){
                try{
                    arg.getValue().sendMessage(message);
                }catch(Exception e){
                    e.printStackTrace();
                }
            }
        });
    }
}

 

 

 

테스트 방법

1. chrome에서 제공하는 확장 프로그램 이용

https://chromewebstore.google.com/detail/websocket-test-client/fgponpodhbmadfljofbimhhlengambbn?pli=1

 

2. postman에서 제공하는 웹소켓 연결 기능 이용

반응형
반응형

Spring boot 기반의 웹 어플리케이션에서 Amazon MSK 을 IAM 인증방식으로 연동하려고 함

MSK 클러스터 접속에 사용할 IAM 계정 : test-user ( MSK 관련 권한이 부여되어 있어야 함)

 

사용한 라이브러리

implementation 'org.springframework.kafka:spring-kafka:3.0.12'
implementation 'software.amazon.msk:aws-msk-iam-auth:2.0.3'

 

메세지 전송용 ProducerFactory 생성

KafkaAdmin 클라이언트는 생성하지 않았음
 * ProducerFactory : Kafka Producer 인스턴스를 생성하는 팩토리 빈
* Kafka Producer : Kafaka 브로커에 메시지를 전송하는 역할을 담당

@Bean
public ProducerFactory<String, String> producerFactory() {
     
        Map<String, Object> configProps = new HashMap<>();
        
        //연결할 kafka 브로커설정(MSK 서버)
        configProps.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, kafkaBootstrapServers);
        
        //보내는 메시지 타입 설정     
        configProps.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
        configProps.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);  

        //IAM인증 설정
        configProps.put(AdminClientConfig.SECURITY_PROTOCOL_CONFIG, "SASL_SSL");
        configProps.put(SaslConfigs.SASL_MECHANISM, "AWS_MSK_IAM");
        //awsProfileName 으로 계정명 명시
        configProps.put(SaslConfigs.SASL_JAAS_CONFIG,"software.amazon.msk.auth.iam.IAMLoginModule required awsProfileName=\"test-user\";");
        configProps.put(SaslConfigs.SASL_CLIENT_CALLBACK_HANDLER_CLASS, "software.amazon.msk.auth.iam.IAMClientCallbackHandler");
                
        return new DefaultKafkaProducerFactory<>(configProps);
}

//위에서 생성한 ProducerFactory로 KafkaTemplate 생성
@Bean
 public KafkaTemplate<String, String> kafkaTemplate() {
        return new KafkaTemplate<>(producerFactory());
}

토픽 생성은 하지 않았음. 특정 토픽으로 메세지를 보낼 때 해당토픽이 존재하지 않으면 자동으로 토픽이 생성됨
auto.create.topics.enable 설정이 디폴트로 true 값을 가짐 

 

메세지 전송용 Service 생성

@Service
public class KafkaProduceService {
    @Autowired
    private KafkaTemplate<String, String> kafkaTemplate;

	//이 메소드를 호출해서 메세지를 보내면 됨
    public void sendMessage(String topic, String message) {
        kafkaTemplate.send(topic, message);
    }  

}

 

 

 

반응형

 

 

메세지 수신용 ConsumerFactory 생성

 * ConsumerFactory : Kafka Consumer 인스턴스를 생성하기 위한 팩토리 빈

 @Bean
 public ConsumerFactory<String, String> consumerFactory() {

        Map<String, Object> configProps = new HashMap<>();
        //consumer Group Id 부여해주기
        configProps.put(ConsumerConfig.GROUP_ID_CONFIG, "testGroup1");
        
        //MSK 브로커 설정(MSK서버)
        configProps.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, kafkaBootstrapServers);

	//IAM 인증 
        configProps.put(AdminClientConfig.SECURITY_PROTOCOL_CONFIG, "SASL_SSL");
        configProps.put(SaslConfigs.SASL_MECHANISM, "AWS_MSK_IAM");
        configProps.put(SaslConfigs.SASL_JAAS_CONFIG,"software.amazon.msk.auth.iam.IAMLoginModule required awsProfileName=\"test-user\";");
        configProps.put(SaslConfigs.SASL_CLIENT_CALLBACK_HANDLER_CLASS, "software.amazon.msk.auth.iam.IAMClientCallbackHandler");

        configProps.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
        configProps.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
        
        return new DefaultKafkaConsumerFactory<>(configProps);
 }

   
//kafka 메시지를 수신하는 리스너 컨테이너를 생성하는데 사용되는 인터페이스
@Bean
public ConcurrentKafkaListenerContainerFactory<String, String> kafkaListenerContainerFactory() {
        ConcurrentKafkaListenerContainerFactory<String, String> factory =
                new ConcurrentKafkaListenerContainerFactory<>();
        factory.setConsumerFactory(consumerFactory());
        return factory;
}

 

 

메세지 수신용 Service 생성

@Service
public class KafkaConsumeService {
    @Autowired
    private KafkaTemplate<String, String> kafkaTemplate;
    
    @KafkaListener(topics = "testTopic", groupId = "testGroup1")
    public void listen(ConsumerRecord<String, String> record) {
        System.out.println(record.value());
        System.out.println(record.toString());
    }

}

 


MSK 에 IAM 인증 시도시 오류 정리

1. IAM 계정의 access key와 secret key를 찾지 못하는 경우

com.amazonaws.SdkClientException: Unable to load AWS credentials from any provider in the chain: [software.amazon.msk.auth.iam.internals.EnhancedProfileCredentialsProvider@3bf917a2: Profile file contained no credentials for profile 'testUser': ProfileFile(profilesAndSectionsMap=[]), cohttp://m.amazonaws.auth.AWSCredentialsProviderChain@2dadd688: Unable to load AWS credentials from any provider in the chain: [EnvironmentVariableCredentialsProvider: Unable to load AWS credentials from environment variables (AWS_ACCESS_KEY_ID (or AWS_ACCESS_KEY) and AWS_SECRET_KEY (or AWS_SECRET_ACCESS_KEY)), SystemPropertiesCredentialsProvider: Unable to load AWS credentials from Java system properties (aws.accessKeyId and aws.secretKey), WebIdentityTokenCredentialsProvider: You must specify a value for roleArn and roleSessionName, software.amazon.msk.auth.iam.internals.EnhancedProfileCredentialsProvider@68aac71d: Profile file contained no credentials for profile 'default': ProfileFile(profilesAndSectionsMap=[]), cohttp://m.amazonaws.auth.EC2ContainerCredentialsProviderWrapper@69883287: Failed to connect to service endpoint: ]]

시스템 환경변수에 AWS_ACCESS_KEY_ID(혹은 AWS_ACCESS_KEY ) 와 AWS_SECRET_KEY (혹은 AWS_SECRET_ACCESS_KEY) 이름으로 access key와 secret key를 세팅해준다. 

혹은 producerFactory와 consumerFactory 내에 System.setProperty로 키 세팅해주면 됨.

System.setProperty("aws.accessKeyId", awsAccessKey);
System.setProperty("aws.secretKey", awsSecretKey);

 

 

2. IAM인증 설정이 바르지 않을때 난 에러 

Node -1 disconnected.
2024-01-31 10:16:24,725 [INFO  ] NetworkClient.cancelInFlightRequests(NetworkClient.java:344) - [Producer clientId=producer-1] Cancelled in-flight API_VERSIONS request with correlation id 1 due to node -1 being disconnected (elapsed time since creation: 98ms, elapsed time since send: 98ms, request timeout: 30000ms)
2024-01-31 10:16:24,726 [WARN  ] NetworkClient$DefaultMetadataUpdater.handleServerDisconnect(NetworkClient.java:1105) - [Producer clientId=producer-1] Bootstrap broker sdfssdfdf.kafka.ap-northeast-2.amazonaws.com:9098 (id: -1 rack: null) disconnected

Factory 생성 시에 sasl_jaas_config 값에 IAM로그인모듈 설정과 IAM계정의 이름을 적어주자.

configs.put(SaslConfigs.SASL_JAAS_CONFIG,"software.amazon.msk.auth.iam.IAMLoginModule required awsProfileName=\"testUser\";");

 

반응형
반응형

32인치 게이밍모니터 알파스캔 콘퀘스트 32U90G 구매 후기

1. 그래픽카드 GTX1050 사용중, DP 연결 

3840x2160, 2560x1600 은 98Hz 까지밖에 안나오고 

2560x1440 으로 144Hz 까지 나옴. 글카를 바꿀때가 됐다.

2. FHD 144hz 27인치 모니터 사용할때보다 pc 켜지는게 늦다. 왠지는 나도 모름. pc를 바꿀때가 됐다.

3. 모니터 높이 조절이 간편함.  그냥 위아래로 밀면 알아서 고정되는것이 신기함.

4. 옵션 등의 메뉴 버튼은 우측에 달려있어 왼손잡이는 불편함. 

5. 플스와 PC 둘다 연결해놨는데 입력을 DP->HDMI1로 사용하다가 HDMI의 전원을 끌 경우 자동으로 DP로 안넘어가는게 아쉽. HDMI1로 입력을 변경하고 나서 다시 자동으로 해놔야함.

6. 내장 스피커가 아주 만족스럽다. 저가 스피커보다 훨 낫다.

 

굉장히 만족중 

반응형

+ Recent posts