Выжимаем из сервера максимум!
Приёмы кеширования и передачи данных на Java


                  Андрей Паньгин
          Одноклассники, ведущий инженер
Выжимаем из сервера максимум
•   Архитектура Одноклассников
•   Сокеты в Java: проблемы и решения
•   Механизм сериализации
•   Кеширование и разделяемая память
Серверы Одноклассников
• 4000 серверов
  – Web, Business logic, Download, Storage
  – Все ПО написано на Java
• Высоконагруженный сервер
  – 20 тыс. одновременных подключений
  – 30 тыс. запросов в секунду
  – Трафик до 1 Gb/s
Remote Service
• Компоненты портала как отдельные сервисы:
   – система сообщений, граф дружб, поиск, лента…
• Внутренняя коммуникация между сервисами

                  long[] friendIds = getConnections (userId)
                                                               Graph cluster
 Business logic
    server
                  List<User> friends = getUsers (friendIds)
                                                               User service
                                                                 cluster
RPC сценарий

    Read Request        byte[] request = connection.read(…);

Deserialize arguments   request → Method method, Object[] args

   Invoke method        Object result = method.invoke(args);

   Serialize result     result → byte[] response

   Send response        connection.write(response);
Выжимаем из сервера максимум
•   Архитектура Одноклассников
•   Сокеты в Java: проблемы и решения
•   Механизм сериализации
•   Кеширование и разделяемая память
Разработка сервера на Java
• java.net (Socket I/O)
  – Поток на каждое соединение
  – Максимум 10 тыс. потоков
• NIO
  – Неблокирующие операции read / write
  – Selector
• NIO frameworks
  – Apache MINA: http://mina.apache.org
  – Netty: http://netty.io
Blocking Socket Selector (BLOS)
• Сочетает преимущества Selector + Blocking I/O
• Поддерживается на уровне ОС, но не в Java
  Linux       BSD          Solaris      Windows
  epoll       kqueue       /dev/poll    Completion ports




• JNI обертки над системными вызовами
 Socket → SocketImpl impl → FileDescriptor fd → int fd
Архитектура BLOS сервера
                     Acceptor thread

                         Selector 1
                         Selector 2


 read      deserialize       invoke      serialize       send
    read       deserialize      invoke       serialize      send

                    Worker thread pool
Проблемы работы с сетью в Java
• Socket
  – Finalizers => утечка памяти, влияние на GC
  – Не поддерживается прямой доступ к памяти
• SocketChannel (NIO)
  – Не срабатывают таймауты на I/O операциях
  – Возможна утечка нативной памяти
• Решение: JNI библиотека
  – Такой подход используется в Tomcat (APR connector)
  – Управление сокетами вручную
Выжимаем из сервера максимум
•   Архитектура Одноклассников
•   Сокеты в Java: проблемы и решения
•   Механизм сериализации
•   Кеширование и разделяемая память
Важность сериализации в RPC
• Затрачиваемое время на преобразование
• Объем передаваемых по сети данных
                   Read Request

               Deserialize arguments

                  Invoke method

                  Serialize result

                  Send response
Способы сериализации в Java
• Стандартная Java сериализация
   – Медленная
   – Большой объем получаемых данных
• JBoss serialization
   – Не поддерживает изменения внутри класса:
     добавление поля, удаление, изменение типа
     или порядка полей
• Avro, Thrift, Protocol Buffers
• Ручная сериализация – Externalizable
   – Не вариант (тысячи классов!)
Архитектура сериализации
• Типы сериализаторов
  – Встроенные (примитивные типы, массивы, коллекции)
  – Генерируемые по полям класса
• Сериализатор имеет уникальный ID –
  64-битный хеш от имен, типов и порядка полей
  class Event {
     String name = “HighLoad++”;
     int year = 2012;              UID   10 H i g h L o a d + + 2012 1
     boolean started = true;
  }
Процедура сериализации
• Запись объекта:
  1. Serializer serializer = Repository.getByClass(obj.getClass());
  2. outputStream.writeLong(serializer.uid);
  3. serializer.write(obj, outputStream);
• Чтение объекта:
  1. long uid = inputStream.readLong();
  2. Serializer serializer = Repository.getById(uid);
      »   may throw SerializerNotFoundException
  3. Object obj = serializer.read(inputStream);
Особенности реализации на Java
• Чтение и запись private полей чужих классов
  – Reflection (медленно!)
  – sun.misc.Unsafe
• Создание экземпляров класса
  – sun.misc.Unsafe.allocateInstance()
  – Динамическая генерация байткода
    ASM framework: http://asm.ow2.org
• Обход Java верификатора
  – Наследование “магического” класса
    sun.reflect.MagicAccessorImpl
Производительность
• Среднее время запроса: -20%
• Сетевой трафик: -50%
Выжимаем из сервера максимум
•   Архитектура Одноклассников
•   Сокеты в Java: проблемы и решения
•   Механизм сериализации
•   Кеширование и разделяемая память
Кеширование
• Что кешировать?
  – Данные из медленного хранилища (БД)
  – Результаты вычислений
• Где кешировать?
  – В оперативной памяти
    • Java Heap (не подходит для объемов > 10 GB)
    • Off-heap memory
  – На диске или флеш-накопителе
Доступ к off-heap памяти в Java
• Native код (JNI обертки над malloc / free)
  – Платформозавимый
• ByteBuffer.allocateDirect
  – Размер буфера ≤ 2 GB
  – Освобождается автоматически при GC
  – Освобождение вручную:
    ((sun.nio.ch.DirectBuffer) buf).cleaner().clean();
• MappedByteBuffer (FileChannel.map)
• Unsafe.allocateMemory
Снимки (shapshots)
• Решают проблему холодного старта
• Должны быть целостными
• Способы создания снимков
  – “Stop-the-world”
  – Запись по сегментам
  – Memory-mapped files (MappedByteBuffer.force)
  – Copy-on-write (fork trick)
  – Shared memory objects
Shared Memory
• Механизм IPC
  – POSIX: shm_open + mmap
  – Windows: CreateFileMapping + MapViewOfFile
  – Linux: /dev/shm
• Создание объекта Shared Memory в Java
  – new RandomAccessFile("/dev/shm/cache", "rw");
• Отображение в адресное пространство процесса
  – легальный способ: FileChannel.map
  – хитрый способ: sun.nio.ch.FileChannelImpl,
    методы map0 и unmap0
Реализация off-heap кеша в Java
•   Непрерывная область памяти
•   Собственный аллокатор
•   FIFO (проще, чем LRU)
•   sun.misc.Unsafe (для быстрого доступа)
•   Shared Memory (/dev/shm)
•   Сегментирование ключей по хеш-коду
•   Блокировка сегмента через ReadWriteLock
    или Semaphore
Структура кеша
Сравнение производительности
• Условия
  – Linux JDK7u4 64-bit
  – 1 млн. операций
  – Значения от 0 до 8 KB
Спасибо!
• Примеры
  – https://github.com/odnoklassniki/shared-memory-cache.git
  – https://github.com/odnoklassniki/rmi-samples.git
• Блог
  – http://habrahabr.ru/company/odnoklassniki/blog/
• Контакты
  – andrey.pangin@corp.mail.ru
• Работа в Одноклассниках
  – http://v.ok.ru

Выжимаем из сервера максимум (Андрей Паньгин)

  • 1.
    Выжимаем из серверамаксимум! Приёмы кеширования и передачи данных на Java Андрей Паньгин Одноклассники, ведущий инженер
  • 2.
    Выжимаем из серверамаксимум • Архитектура Одноклассников • Сокеты в Java: проблемы и решения • Механизм сериализации • Кеширование и разделяемая память
  • 3.
    Серверы Одноклассников • 4000серверов – Web, Business logic, Download, Storage – Все ПО написано на Java • Высоконагруженный сервер – 20 тыс. одновременных подключений – 30 тыс. запросов в секунду – Трафик до 1 Gb/s
  • 4.
    Remote Service • Компонентыпортала как отдельные сервисы: – система сообщений, граф дружб, поиск, лента… • Внутренняя коммуникация между сервисами long[] friendIds = getConnections (userId) Graph cluster Business logic server List<User> friends = getUsers (friendIds) User service cluster
  • 5.
    RPC сценарий Read Request byte[] request = connection.read(…); Deserialize arguments request → Method method, Object[] args Invoke method Object result = method.invoke(args); Serialize result result → byte[] response Send response connection.write(response);
  • 6.
    Выжимаем из серверамаксимум • Архитектура Одноклассников • Сокеты в Java: проблемы и решения • Механизм сериализации • Кеширование и разделяемая память
  • 7.
    Разработка сервера наJava • java.net (Socket I/O) – Поток на каждое соединение – Максимум 10 тыс. потоков • NIO – Неблокирующие операции read / write – Selector • NIO frameworks – Apache MINA: http://mina.apache.org – Netty: http://netty.io
  • 8.
    Blocking Socket Selector(BLOS) • Сочетает преимущества Selector + Blocking I/O • Поддерживается на уровне ОС, но не в Java Linux BSD Solaris Windows epoll kqueue /dev/poll Completion ports • JNI обертки над системными вызовами Socket → SocketImpl impl → FileDescriptor fd → int fd
  • 9.
    Архитектура BLOS сервера Acceptor thread Selector 1 Selector 2 read deserialize invoke serialize send read deserialize invoke serialize send Worker thread pool
  • 10.
    Проблемы работы ссетью в Java • Socket – Finalizers => утечка памяти, влияние на GC – Не поддерживается прямой доступ к памяти • SocketChannel (NIO) – Не срабатывают таймауты на I/O операциях – Возможна утечка нативной памяти • Решение: JNI библиотека – Такой подход используется в Tomcat (APR connector) – Управление сокетами вручную
  • 11.
    Выжимаем из серверамаксимум • Архитектура Одноклассников • Сокеты в Java: проблемы и решения • Механизм сериализации • Кеширование и разделяемая память
  • 12.
    Важность сериализации вRPC • Затрачиваемое время на преобразование • Объем передаваемых по сети данных Read Request Deserialize arguments Invoke method Serialize result Send response
  • 13.
    Способы сериализации вJava • Стандартная Java сериализация – Медленная – Большой объем получаемых данных • JBoss serialization – Не поддерживает изменения внутри класса: добавление поля, удаление, изменение типа или порядка полей • Avro, Thrift, Protocol Buffers • Ручная сериализация – Externalizable – Не вариант (тысячи классов!)
  • 14.
    Архитектура сериализации • Типысериализаторов – Встроенные (примитивные типы, массивы, коллекции) – Генерируемые по полям класса • Сериализатор имеет уникальный ID – 64-битный хеш от имен, типов и порядка полей class Event { String name = “HighLoad++”; int year = 2012; UID 10 H i g h L o a d + + 2012 1 boolean started = true; }
  • 15.
    Процедура сериализации • Записьобъекта: 1. Serializer serializer = Repository.getByClass(obj.getClass()); 2. outputStream.writeLong(serializer.uid); 3. serializer.write(obj, outputStream); • Чтение объекта: 1. long uid = inputStream.readLong(); 2. Serializer serializer = Repository.getById(uid); » may throw SerializerNotFoundException 3. Object obj = serializer.read(inputStream);
  • 16.
    Особенности реализации наJava • Чтение и запись private полей чужих классов – Reflection (медленно!) – sun.misc.Unsafe • Создание экземпляров класса – sun.misc.Unsafe.allocateInstance() – Динамическая генерация байткода ASM framework: http://asm.ow2.org • Обход Java верификатора – Наследование “магического” класса sun.reflect.MagicAccessorImpl
  • 17.
    Производительность • Среднее времязапроса: -20% • Сетевой трафик: -50%
  • 18.
    Выжимаем из серверамаксимум • Архитектура Одноклассников • Сокеты в Java: проблемы и решения • Механизм сериализации • Кеширование и разделяемая память
  • 19.
    Кеширование • Что кешировать? – Данные из медленного хранилища (БД) – Результаты вычислений • Где кешировать? – В оперативной памяти • Java Heap (не подходит для объемов > 10 GB) • Off-heap memory – На диске или флеш-накопителе
  • 20.
    Доступ к off-heapпамяти в Java • Native код (JNI обертки над malloc / free) – Платформозавимый • ByteBuffer.allocateDirect – Размер буфера ≤ 2 GB – Освобождается автоматически при GC – Освобождение вручную: ((sun.nio.ch.DirectBuffer) buf).cleaner().clean(); • MappedByteBuffer (FileChannel.map) • Unsafe.allocateMemory
  • 21.
    Снимки (shapshots) • Решаютпроблему холодного старта • Должны быть целостными • Способы создания снимков – “Stop-the-world” – Запись по сегментам – Memory-mapped files (MappedByteBuffer.force) – Copy-on-write (fork trick) – Shared memory objects
  • 22.
    Shared Memory • МеханизмIPC – POSIX: shm_open + mmap – Windows: CreateFileMapping + MapViewOfFile – Linux: /dev/shm • Создание объекта Shared Memory в Java – new RandomAccessFile("/dev/shm/cache", "rw"); • Отображение в адресное пространство процесса – легальный способ: FileChannel.map – хитрый способ: sun.nio.ch.FileChannelImpl, методы map0 и unmap0
  • 23.
    Реализация off-heap кешав Java • Непрерывная область памяти • Собственный аллокатор • FIFO (проще, чем LRU) • sun.misc.Unsafe (для быстрого доступа) • Shared Memory (/dev/shm) • Сегментирование ключей по хеш-коду • Блокировка сегмента через ReadWriteLock или Semaphore
  • 24.
  • 25.
    Сравнение производительности • Условия – Linux JDK7u4 64-bit – 1 млн. операций – Значения от 0 до 8 KB
  • 26.
    Спасибо! • Примеры – https://github.com/odnoklassniki/shared-memory-cache.git – https://github.com/odnoklassniki/rmi-samples.git • Блог – http://habrahabr.ru/company/odnoklassniki/blog/ • Контакты – andrey.pangin@corp.mail.ru • Работа в Одноклассниках – http://v.ok.ru