본문 바로가기

TroubleShooting/Etc

MsgPack 을 이용해서 직접 write 구현하기

728x90

MsgPack 을 이용해서 직접 write 구현하기

 

MsgPack 를 이용해서 직접 byte 배열에다가 쓸 경우 다음 규칙을 지키자.

 

 

1. 객체 타입일 경우 멤버 변수의 갯수가 몇개인지 write 한다. - writeArrayBegin(N)

2. list(또는 array) 타입일 경우 list 의 size 를 write 한다. - writeArrayBeging(N)

2.1 list(또는 array) size 만큼 loop 를 돌면서 각 element 를 write 한다.

2.2 list(또는 array) 의 element 가 primitive 타입이 아닌 객체형일 경우에는 1 번에 해당하므로 객체 멤버 변수 갯수를 write 한다.

2.3 list(또는 array) element 의 size 가 0 일 경우에는 writeArrayBegin(0) 을 write 한다.

3. 숫자 타입일 경우, write(:integer) 을 사용하는데 반드시 인자값이 int(Integer) 여야 한다.

4. 그외 나머지는 write(:string) 를 쓴다.

5. null 값일 경우에는 writeNil() 을 쓴다.

 

ex)

 

- BoxData 데이터

idx

 item_id

 box_type

 rew_list

 rew_value

 rew_rate

 select_cnt

 select_list

 select_value

 1

 9000

 0

 11|21|14|26|30010

 1|2|1|3|1

 5|4|3|2|1

 0

 0

 0

 2

 9001

 0

 10|16|10006|19|25

 3|3|1|1|1

 5|4|3|2|1

 0

 0

 0

 3

 9002

 1 

 0

 0

 0

 3

 2|5|7|9|10001|10002|10005

 3|3|3|3|1|1|1

 4

 9003

 1

 0

 0

 0

 2

 2|5|7|9|10001|10002|10005

 3|3|3|3|1|1|1

 

- BoxData 클래스

@Data
@Message    
public class BoxData {
    private int idx;
    private int itemId;
    private int boxType;
    private List<Integer> rewList;
    private List<Integer> rewValue;
    private List<Integer> rewRate;
    private int selectCnt;
    private List<Integer> selectList;
    private List<Integer> selectValue;
}

- BoxDataWrapper 클래스

@Data
@Message       
public class BoxDataWrapper {
    private List<BoxData> elementList;
}

BoxDataWrapper 객체를 MsgPack 으로 serialize 하기 위해서는 다음과 같은 과정을 거쳐야한다.

1. BoxDataWrapper 의 멤버 갯수가 1 개(LIst<BoxData> elements)라서 array 가 1 이라고 write.

    packer.writeArrayBegin(1);

2. 멤버변수인 elements 가 List 라서 list size 를 write.

    packer.writeArrayBegin(elements.size());

3. loop 를 돌면서 BoxData 를 write 한다.

    for (int i = 0; i < elements_size; i++) {

    ...

    }

3.1 loop 안에서 BoxData 이 객체이고 BoxData 의 멤버 갯수가 9 라서 array 를 9 라고 write.

    for (int i = 0; i < elements_size; i++) {

        packer.writeArrayBegin(9);

        ...

    }

3.2 이제 각각의 BoxData 객체들을 write 한다.

    packer.writeArrayBegin(9);

 

    BoxData boxData = elements.get(0);

    write(boxData.getIdx);    // idx

    write(boxData.getItemId);    // itemId

    writeArrayBegin(5);           // rewList list 시작

    for (Integer data : boxData.getRewList() {     // 11|21|14|26|30010 write

         write(data);

    }

    writeArrayEnd();              // 반드시 writeArrayEnd() 를 호출해야한다. packer 내부에 stack count 를 더하기/빼기를 하고 있음.

    for (Integer data : boxData.getRewValue() {     // 1|2|1|3|1   write

         write(data);

    }

    writeArrayEnd();

    ...

    writeArrayBegin(0);            // selectList 는 리스트 요소가 아무것도 없는 경우라서(selectList = new ArrayList<>()) size 0 인 list 라고 표시.

    writeArrayEnd();

 

4. BoxData 객체 write 끝나면 end 표시를 한다. packer 내부의 count 를 stack 으로 관리하고 있다. writeArrayEnd() 를 안하면 loop 카운트가 128 이 넘는 순간 boundary index 오류를 보게된다.

    packer.writeArrayEnd();

 

- C# 에서 위의 msgpack 으로 serialize 한 파일(BoxData.file) 을 deserialize 하는 예제

using MessagePack;                // MessagePack by neuecc v1.7.3.4

[MessagePackObject]
public class BoxDataWrapper
{
    [Key(0)]
    public List<BoxData> listElement { get; set; }
}

[MessagePackObject]
public class BoxData
{
    [Key(0)]
    public int idx { get; set; }

    [Key(1)]
    public int itemId { get; set; }

    [Key(2)]
    public int boxType { get; set; }

    [Key(3)]
    public List<int> rewList { get; set; }

    [Key(4)]
    public List<int> rewValue { get; set; }

    [Key(5)]
    public List<int> rewRate { get; set; }

    [Key(6)]
    public int selectCnt { get; set; }

    [Key(7)]
    public List<int> selectList { get; set; }

    [Key(8)]
    public List<int> selectValue { get; set; }
}

string filePath = "d:\\Data\\BoxData.file";
byte[] bytes = File.ReadAllBytes(filePath);

var wrapper = MessagePackSerializer.Deserialize<BoxDataWrapper>(bytes);

foreach (Model model in wrapper.elementList)
{
    Console.WriteLine(model.idx + ":" + model.itemId + ":" + model.boxType);
}

- 시나리오

1. BoxData 데이터를 읽어서 BoxDataWrapper 객체 형태로 msgpack.write() 를 해서 byte 배열을 구한다.

2. C# 으로 BoxDataWrapper 로 unpack(deserialize) 해서 BoxData 데이터 값이 나오는지 확인한다.

 

※ java 에서 MsgPack 사용할때 주의할 점

 객체를 byte 배열로 바꿀때, new 로 생성한 MessagePack 객체로 write 하는 방식은 thread safe 하지 않아서 한꺼번에 많은 객체를 serialize 할때, 특정 시점이 지나면 lock 이 발생한다. 프로젝트에서 jstack 으로 확인했다. write() 코드를 확인해서 trace 해보면 synchronized 를 사용한 메소드가 계속 보인다.

MessagePack msgpack = new MessagePack();

byte[] bytes = msgpack.write(object);

 

 그래서 이 블로그 (참조한 글에 있는)에서는 Packer 를 사용하는 방식을 추천해서 적용해보았더니, CPU 사용량도 줄어들고 lock 도 발생을 안했다.

MessagePack msgpack = new MessagePack(); // singleton

Packer packer = msgpack.createPacker(outputStream); // createPacker every time

packer.write(object);

 

참조한 글

http://bugreporter.blogspot.com/2014/02/messagepack-java-permgen-memory-error.html