내용 보기

작성자

관리자 (IP : 106.247.248.10)

날짜

2024-04-01 08:33

제목

[인터뷰 질문] [스크랩] 백엔드 질문들 (Java, Spring)


L, N, K 사 인터뷰 질문


A

오라클 조인 종류?

간단하게 Inner, Outer, Left, Right [1] 등에 대한 질문이었음

과도하게 Broadcast Join, Nested loop 등을 생각하느라 답변을 잘 하지 못함

톰캣 ajp Connector 말고 다른 Connector?

8기준 크게 HTTP Connector들(NIO, NIO2, APR) [3]과 AJP Connector 존재.

톰캣의 구조?

Service, Engine, Host, Context, Connector 등의 객체로 구성된 구조. 서블릿 컨테이너 명세를 구현한 것.



Image from [2]

스택 2개로 큐 만들기? 성능 개선 방법?

하나는 insert용, 하나는 뒤짚기용 [4]. 성능 개선은 lazy 정도 밖에 떠오르지 않았음.

Connection에서 Timeout 종류 [5]

connect: 3-way handshake가 timeout 시간 안에 끝나야 함

read: 클라이언트가 서버로부터 timeout 시간 안에 응답을 받아야 함

write: 클라이언트가 서버에 timeout 시간 안에 성공적으로 write해야 함

HTTP keep-alive (client side): HTTP 헤더 일부로 클라이언트가 timeout 시간 안에 서버의 응답을 받지 못하면 서버가 살아 있는지 체크하기 위해 요청을 보냄. 죽었으면 Connection 끊음

Request (server side HTTP): 서버가 클라이언트로부터 timeout 시간 이상 데이터를 받지 않으면 서버는 Connection을 드랍함 (용어가 정확한지 모르겠음, HTTP keep-alive를 서버 관점에서 보는 것인 듯 함)

Web Server의 Timeout이 매우 길고 요청이 무수히 많고 쓰레드 풀도 무한이며 작업은 그냥 무한한 sleep이라면, 가장 먼저 고갈되는 자원은?

아마도 CPU-bound로 생각됨 [6]

TCP Keepalive 길때 생기는 문제는?

새롭게 진입하려는 커넥션이 자원을 사용하지 못해 생성되지 못함.

스프링 MVC 동작 방법?

서블릿 구현체로 서블릿 - DispatcherServlet - Controller 간의 요청, 응답을 주고 받음.


Image from [7]

MQ 쓰는 이유?

Kafka를 만든 Jay Kreps 글에서 로그의 역할과 관련되어 답변하려 했음 (이미지가 떠올랐음).

MQ이기에 Kafka와 같은 장점은 많은 제약이 있어서(여러번 consume, 복잡도 줄이는 것? 등), 앞뒤 간의 의존성을 없앨 수 있는 부분이 크다고 답변함. (대략 decouple이라는 점과 상통하는 듯 [9])


Before - Image from [8]

After - Image from [8]

이벤트 소싱 형태에서 데이터의 상태를 복원하는 방법?

Write-ahead log 같은 것을 이벤트 소싱 형태로 스트림으로 흘려주고, replay를 통해 복제할 수 있다고 함.

CDC와 같은 도구들도 언급 [10].

B

Interface vs abstract class [12]

가볍게 이해하고 있는 부분만 언급했는데, default method를 예로들며 추가 질문이 들어왔음. Polymorphism과 연관되어 어떠한 답변을 기대한 듯 하였음.

Abstract class는 언제 쓸까?

  • 객체가 무엇인가와 관련되어 사용 [13]
  • 상태와 기능을 공유하려 할 때 [14]
  • 상속 개념을 사용할 때 (인터페이스의 default method로 무색하긴 하나 [12])
  • non-public 멤버를 선언하려할 때 (인터페이스에서는 public이어야 함)
  • 추후에 새로운 메소드들을 더할 때. 인터페이스에 새로운 메소드를 추가하면, 해당 인터페이스 implement한 모든 클래스가 새로운 메소드를 구현해야 되기에.
  • 컴포넌트의 여러 버젼을 생성하려 할 때. 추상 클래스만 업데이트하면 상속하는 모든 클래스가 변경됨. 인터페이스를 쓰면, 일단 한 번 생성하면 변경하기 어렵기에 새로운 버젼을 위해 새로운 인터페이스를 생성하여야 함.
  • 좀 더 뛰어난 forward 호환성 이점을 제공. 일단 클라이언트가 인터페이스를 사용하기 시작하면, 그것을 변경하기는 매우 어려움.
  • 여러 컴포넌트들이 사용하는 공통된 기능을 제공하고자 할 때.

Interface는 언제 쓸까?

  • 객체가 무엇을 할 수 있는지와 관련되어 사용 (일종의 contract) [13]
  • 상태와 기능에 대한 promise를 제공하기 위해 [14]
  • 연관성이 약한 여러 객체들에 일반적인 기능들이라면 인터페이스를 사용.
  • API가 변경되지 않는다고 생각되면 좋은 선택
  • 다중 상속과 비슷한 요구사항을 원할 때
  • 작고 간결한 기능 모음을 디자인 할 때 사용

브라우저를 켜서 특정 사이트 접속 시 발생하는 일을 OSI 7계층 관점에 기반해 설명해달라

[31, 32]

mutex와 semaphore의 차이는? [27, 28, 29, 30]

mutex

mutex는 mutual exclusion을 제공하는 locking mechanism입니다. critical region은 task한 task가 '소유하게(Principle of Ownership)' 됩니다.

mutex는 이러한 ownership의 존재로 인해 semaphore의 아래와 같은 문제점을 해결해줍니다:

  • Accidental release
  • recursive deadlock
  • deadlock through Task Death: Death detection을 통해
  • priority inversion
  • semaphore as a signal: mutex는 ownership으로 인해 synchronization에 사용될 수 없음. 그렇기에 사용처를 명확히 함.

몇몇 OS 내부의 mutex는 구현에 따라 Recursion, Priority inheritance, Death Detection을 지원하거나 지원하지 않을 수 있습니다.

semaphore

semaphore는 signaling mechanism으로 resource를 접근으로부터 protecting하지 않습니다. Semaphore의 Giving과 Taking은 근본적으로 decoupled되어 있습니다. 크게 binary와 counting semaphore가 존재합니다.

예로, 주차장을 관리하는 시스템의 경우 처음에 주차장 갯수를 count에 설정합니다. 이후 자리가 사용되면서 count는 줄어들게 되는데요. count가 0이 되면 다음 차는 주차가 blocked됩니다. 이후 차 1대가 나가면서 count는 1로 증가하고 기다리던 차가 사용할 수 있게 됩니다.

좀 더 시그널링적인 측면을 잘 나타내고 있는 예는 아래와 같습니다:

   Task A                      Task B
   ...                         Take BinSemaphore   <== wait for something
   Do Something Noteworthy
   Give BinSemaphore           do something    <== unblocks

Task B는 특정 이벤트 발생을 기다립니다. Task A는 특정 이벤트 발생을 알리기 위해 어떠한 액션을 취하고, Task B는 그러한 부분을 확인하게 됩니다.

특히, binary semaphore에서 B가 semaphore를 take한 후에 A가 semaphore를 놓아주었다는 부분입니다. 이렇게 Binary semaphore는 리소스 접근에 대한 protecting을 하지 않습니다.

AOP란? AOP의 주된 패턴은?

개념은 잘 말했는데 안써봐서 그런지 패턴은 무엇이고, 내부는 어떻게 되어 있는지에 대한 질문에 답변 못함.

AOP는 프로그래밍 패러다임으로 cross-cutting하여 특정 '측면'을 분리하고 모듈성을 높일 수 있음 [15].

AOP의 주된 패턴은 Proxy로 Spring AOP는 jdk dynamic proxies를 스탠다드로 사용함. 또한, CGLIB proxies도 사용할 수 있며 AspectJ를 사용해 annotation을 사용할 수 있음 [16].

Proxy 패턴을 보면, 정적 및 동적 프록시 패턴이 있음. [17]

정적은,

직접 @Override하거나

package com.test.proxy;

public class Person {
    public void run(){
        System.out.println("Person Class run method");
    }
}
package com.test.proxy;

public class PersonStaticProxy extends Person {
    @Override
    public void run() {
        System.out.println("Person Class run Before method execution");
        super.run();
        System.out.println("Person Class run After the execution of the method");
    }
}
package com.test.proxy;

public class Test {
    public static void main(String[] args) {
        Person person = new PersonStaticProxy();
        person.run();
    }
}
Person Class run Before method execution
Person Class run method
Person Class run After the execution of the method

인터페이스와 구현체 타겟 클래스를 Proxy 클래스에 embed하여 아래와 같이 구현 가능:

package com.test.proxy;

public interface MathematicDao {
    public void add(int a, int b);
}
package com.test.proxy;

public class MathematicImpl implements MathematicDao {
    @Override
    public void add(int a, int b) {
        System.out.println("a plus b The values of are:" + (a + b));
    }
}
package com.test.proxy;

public class MathematicProxy implements MathematicDao {
    MathematicImpl mathematic;

    public MathematicProxy( MathematicImpl mathematic){
        this.mathematic = mathematic;
    }

    @Override
    public void add(int a, int b) {
        System.out.println("add Before method execution");
        this.mathematic.add(a, b);
        System.out.println("add After the execution of the method");
    }
}
package com.test.proxy;

public class Test {
    public static void main(String[] args) {
        MathematicDao md = new MathematicProxy(new MathematicImpl());
        md.add(11, 22);
    }
}
add Before method execution
a plus b The value of is: 33
add After the execution of the method

그러나 타겟 클래스가 100개면 Proxy 클래스도 100개를 작성해야 되서 중복이 많이 발생함.

동적은 jdk dynamic agent 또는 cglib dynamic agent를 사용해서 구현할 수 있는데, 동적이든 정적이든 타겟 클래스 메소드에 변형을 가하진 않음.

먼저 jdk dynamic agent 기반은:

package com.test.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class DemoProxyHandler implements InvocationHandler{
    private Object obj;
    public DemoProxyHandler(Object obj) {
        this.obj = obj;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        System.out.println(method.getName() + "Before implementation");
        Object res = method.invoke(obj, args);
        System.out.println(method.getName() + "After implementation");

        return res;
    }
}
package com.test.proxy;

public interface MathematicDao {
    public void add(int a, int b);
}
package com.test.proxy;

public class MathematicImpl implements MathematicDao {
    @Override
    public void add(int a, int b) {
        System.out.println("a plus b The values of are:" + (a + b));
    }
}
package com.test.proxy;

import java.lang.reflect.Proxy;

public class Test {
    public static void main(String[] args) {
        MathematicDao m = new MathematicImpl();

        DemoProxyHandler dp = new DemoProxyHandler(m);

        Object obj = Proxy.newProxyInstance(m.getClass().getClassLoader(), m.getClass().getInterfaces(), dp);

        MathematicDao mathematicDao = (MathematicDao) obj;

        mathematicDao.add(1, 3);
    }
}
add Before implementation
a plus b The value of is: 4
add After implementation

cglib은 low-level bytecode 기술을 사용함. bytecode 기술을 통해 클래스에 대한 서브클래스를 생성하고, 서브클래스에서 메소드 인터셉션 기술을 통해 부모 클래스 메소드에 대한 모든 콜을 인터셉트하고 cross-cutting 로직을 수행함:

package com.test.proxy;

public class CglibProxyHandler implements MethodInterceptor{

    private Object target;//The target object of the delegate

    public Object getInstance(Object target) {
        this.target = target;

        Enhancer enhancer = new Enhancer();

        enhancer.setSuperclass(target.getClass());

        enhancer.setCallback(this);

        return enhancer.create();
    }

    @Override
    public Object intercept(Object enhancer, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {

        Object res = null;

        System.out.