복붙노트

[HADOOP] 직렬화 프레임 워크의 호환되지 않는 버전 변경 다루기

HADOOP

직렬화 프레임 워크의 호환되지 않는 버전 변경 다루기

우리는 Kryo (직렬화 프레임 워크)를 사용하여 바이트에 직렬화 된 데이터를 저장하는 Hadoop 클러스터를 가지고있다. 이 작업을 수행하는 데 사용 된 Kryo 버전은 Kryo를 사용하여 경험 한 문제에 대한 자체 패치를 적용하기 위해 공식 릴리스 2.21에서 포크되었습니다. 현재 Kryo 버전 2.22는 이러한 문제를 해결하지만 다른 해결책을 제시합니다. 결과적으로 우리가 사용하는 Kryo 버전을 변경할 수 없습니다. 이는 Hadoop 클러스터에 이미 저장된 데이터를 더 이상 읽을 수 없기 때문입니다. 이 문제를 해결하기 위해 Hadoop 작업을 실행하려고합니다.

문제는 하나의 자바 프로그램 (더 정확하게는 Hadoop 작업의 매퍼 클래스)에서 동일한 클래스의 두 가지 버전을 사용하는 것이 쉽지 않다는 것입니다.

하나의 Hadoop 작업에서 동일한 직렬화 프레임 워크의 서로 다른 두 버전을 사용하여 객체를 비 직렬화하고 직렬화하는 방법은 무엇입니까?

첫 번째 방법은 Maven Shade 플러그인의 재배치 기능을 사용하여 Kryo 지사의 패키지 이름을 바꾸고 다른 이슈 ID로 릴리스하여 변환 작업 프로젝트의 두 아티팩트에 의존 할 수있었습니다. 그런 다음 이전 버전과 새 버전 모두에서 하나의 Kryo 객체를 인스턴스화하고 이전 버전을 사용하여 직렬화를 해제하고 새 객체를 사용하여 객체를 다시 직렬화합니다.

문제 Hadoop 작업에서는 Kryo를 명시 적으로 사용하지 않고 자체 라이브러리의 여러 계층을 통해 액세스합니다. 이 라이브러리들 각각에 대해,

좀 더 지저분 해지기 위해서 다른 써드 파티 라이브러리에서 제공하는 Kryo 시리얼 라이저도 사용합니다.

두 번째 방법은 변환 작업이 포함 된 Maven 프로젝트에서 Kryo를 전혀 사용하지 않고 Hadoop의 분산 캐시에 저장된 각 버전의 JAR에서 필요한 클래스를로드하는 것입니다. 객체를 직렬화하면 (자), 다음과 같이됩니다.

public byte[] serialize(Object foo, JarClassLoader cl) {
    final Class<?> kryoClass = cl.loadClass("com.esotericsoftware.kryo.Kryo");
    Object k = kryoClass.getConstructor().newInstance();
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    final Class<?> outputClass = cl.loadClass("com.esotericsoftware.kryo.io.Output");

    Object output = outputClass.getConstructor(OutputStream.class).newInstance(baos);
    Method writeObject = kryoClass.getMethod("writeObject", outputClass, Object.class);
    writeObject.invoke(k, output, foo);
    outputClass.getMethod("close").invoke(output);
    baos.close();
    byte[] bytes = baos.toByteArray();
    return bytes;
}

문제 이 방법은 구성되지 않은 Kryo 객체를 인스턴스화하고 일부 객체를 직렬화 / 복원하는 작업을 수행 할 수 있지만 훨씬 복잡한 Kryo 구성을 사용합니다. 여기에는 몇 가지 사용자 지정 serializer, 등록 된 클래스 ID 등이 포함됩니다. 예를 들어, NoClassDefFoundError를 얻지 않고도 클래스에 대한 사용자 지정 serializer를 설정하는 방법을 찾을 수 없었습니다. 다음 코드는 작동하지 않습니다.

Class<?> kryoClass = this.loadClass("com.esotericsoftware.kryo.Kryo");
Object kryo = kryoClass.getConstructor().newInstance();
Method addDefaultSerializer = kryoClass.getMethod("addDefaultSerializer", Class.class, Class.class);
addDefaultSerializer.invoke(kryo, URI.class, URISerializer.class); // throws NoClassDefFoundError

마지막 줄은

java.lang.NoClassDefFoundError: com/esotericsoftware/kryo/Serializer

왜냐하면 URISerializer 클래스는 Kryo의 Serializer 클래스를 참조하고 Serializer 클래스를 모르는 고유 한 클래스 로더 (System 클래스 로더)를 사용하여로드하려고하기 때문입니다.

현재 가장 유망한 접근법은 독립적 인 중간 직렬화를 사용하는 것 같습니다. JSON 또는 Gson을 사용하여 다음 두 개의 개별 작업 실행 :

문제 이 솔루션의 가장 큰 문제점은 처리되는 데이터의 공간 소비를 대략 두 배로 늘리는 것입니다. 게다가 모든 데이터에 문제없이 작동하는 또 다른 직렬화 방법이 필요합니다. 먼저 조사해야합니다.

해결법

  1. ==============================

    1.여러 클래스 로더 방식을 사용합니다.

    여러 클래스 로더 방식을 사용합니다.

    (패키지 이름 바꾸기도 가능하지만보기 흉한 것처럼 보이지만 이것은 일회용 해킹이므로 아름다움과 정확성이 뒷자리를 차지할 수 있습니다. 중간 직렬화는 위험한 것처럼 보입니다. Kryo를 사용하는 이유가 있었는데 그 이유는 무효화됩니다. 다른 중간 양식을 사용하여).

    전반적인 디자인은 다음과 같습니다.

    child classloaders:      Old Kryo     New Kryo   <-- both with simple wrappers
                                    \       /
                                     \     /
                                      \   /
                                       \ /
                                        |
    default classloader:    domain model; controller for the re-serialization
    

    고려 사항

    코드 조각에서 각 객체 직렬화 및 직렬화 해제에 대해 하나의 클래스 로더가 작성됩니다. 아마도 클래스 로더를 한 번만로드하고 주요 메소드를 찾고 파일을 반복하고 싶을 것이다.

    for (String file: files) {
        Object graph = mainIn.invoke(null, file + ".in");
        mainOut.invoke(null, graph, file + ".out");
    }
    

    도메인 객체에는 Kryo 클래스에 대한 참조가 있습니까? 그렇다면 어려움이 있습니다.

    두 경우 모두 첫 번째 방법은 이러한 참조를 검토하고 제거하는 것입니다. 이 작업을 수행하는 한 가지 방법은 기본 클래스 로더가 모든 Kryo 버전에 액세스 할 수 없도록하는 것입니다. 도메인 객체가 어떤 식 으로든 Kryo를 참조하면 참조가 실패합니다 (클래스가 직접 참조 된 경우 ClassNotFoundError 또는 반사가 사용되는 경우 ClassNotFoundException).

  2. ==============================

    2.2의 경우, 여기에 표시된대로 serializer 및 serializer의 새 버전과 이전 버전에 대한 모든 종속성을 포함하는 두 개의 jar 파일을 만들 수 있습니다. 그런 다음 별도의 클래스 로더에서 각 코드 버전의 코드를로드하는 map reduce 작업을 만들고 중간에 일부 코드를 추가하여 이전 코드로 비 직렬화 한 다음 새 코드로 직렬화하십시오.

    2의 경우, 여기에 표시된대로 serializer 및 serializer의 새 버전과 이전 버전에 대한 모든 종속성을 포함하는 두 개의 jar 파일을 만들 수 있습니다. 그런 다음 별도의 클래스 로더에서 각 코드 버전의 코드를로드하는 map reduce 작업을 만들고 중간에 일부 코드를 추가하여 이전 코드로 비 직렬화 한 다음 새 코드로 직렬화하십시오.

    도메인 객체가 glue 코드와 같은 클래스 로더에로드되고 serialize / deserialize 할 코드가 동일한 코드 객체와 동일한 클래스 로더에 따라 달라 지므로 둘 다 동일한 도메인 객체 클래스를 볼 수 있도록주의해야합니다.

  3. ==============================

    3.생각하지 않고 올 수있는 가장 쉬운 방법은 변환을 수행하는 추가 Java 응용 프로그램을 사용하는 것입니다. 따라서 바이너리 데이터를 2 차 자바 애플리케이션으로 보낸다. (간단한 로컬 소켓은 트릭을 멋지게 할 것이다.) 그래서 클래스 로더 나 패키지를 조작 할 필요가 없다.

    생각하지 않고 올 수있는 가장 쉬운 방법은 변환을 수행하는 추가 Java 응용 프로그램을 사용하는 것입니다. 따라서 바이너리 데이터를 2 차 자바 애플리케이션으로 보낸다. (간단한 로컬 소켓은 트릭을 멋지게 할 것이다.) 그래서 클래스 로더 나 패키지를 조작 할 필요가 없다.

    생각할 수있는 유일한 것은 중간 표현입니다. 다른 직렬화 메커니즘을 사용하거나 Java의 내부 직렬화를 사용할 시간이없는 경우에는 사용할 수 있습니다.

    두 번째 Java 응용 프로그램을 사용하면 임시 저장 장치를 다루지 않고 모든 것을 메모리에서 처리 할 수 ​​있습니다.

    그리고 일단 그 소켓 + 두 번째 응용 프로그램 코드를 사용하면 편리하게 사용할 수있는 수많은 상황을 발견하게됩니다.

    또한 jGroups를 사용하여 로컬 클러스터를 구축하고 결국 소켓을 사용하여 번거 로움을 줄일 수 있습니다. jGroups는 제가 아는 가장 단순한 통신 API입니다. 논리 채널을 구성하고 누가 합류하는지 확인하십시오. 또한 테스트를 쉽게 해주는 동일한 JVM 내에서 작동하는 것이 가장 좋습니다. 원격으로 실행하면 로컬 응용 프로그램과 동일한 방식으로 다른 물리적 서버를 서로 바인딩 할 수 있습니다.

    다른 대안으로는 ZeroMQ를 ipc (프로세스 간 통신) 프로토콜과 함께 사용하는 것입니다.

  4. from https://stackoverflow.com/questions/16083263/dealing-with-an-incompatible-version-change-of-a-serialization-framework by cc-by-sa and MIT license