...

суббота, 25 февраля 2017 г.

Bridge-domains and virtual-switch in JunOS

image alt

Где то год назад или раньше я писал статью, посвященную routing-instance в JunOS, где описал основные виды routing инстансов, за исключением virtual switch и evpn. О последней вы можете прочитать в статье о EVPN, а вот virtual-switch пока что остался без внимания. Многие недооценивают возможности данной routing instance — а ведь функционал приличен. В данной небольшой статье мы рассмотрим — что такое virtual-switch, с чем его едят и нужен ли он вам.

Перед тем, как перейти непосредственно к обсуждению и конфигурированию виртуального свича, нам надо осветить два очень важных вопроса:
1. Метод (стиль) конфигурирования тегированного интерфейса в JunOS;
2. Что такое bridge-домен.

JunOS предоставляет нам два метода конфигурирования тегированного интерфейса, предназначенного для использования в bridge-домене:
1. Service Provider
2. Enterprise
Остановимся на них по подробнее.

Примечание: во всех примерах использованы интерфейсы с инкапсуляцией flexible-ethernet-services и тегированием flexible-vlan-tagging, что дает максимальную свободу в конфигурировании интерфейсов:
bormoglotx@RZN-PE1> show configuration interfaces ae3 
apply-groups-except CORE; 
description "to RZN-CE1 | ae0";
flexible-vlan-tagging;
encapsulation flexible-ethernet-services;
aggregated-ether-options {
    minimum-links 1;
    link-speed 1g;
    lacp {
        active;
        periodic fast;

Service Provider.

Данный метод конфигурирования интерфейса дает нам полную свободу в манипуляциях с влан тегами, для чего используются операции pop (снятие), push (добавление), swap (замена) тега, а также операции с двумя тегами, например pop-swap производит снятие верхнего тега и замену нижнего при использовании двойного тегирования. Самый простой вариант использования данного метода создания тегированного интерфейса выглядит так:

[edit]
bormoglotx@RZN-PE1# show interfaces ae3.10 
encapsulation vlan-bridge;
vlan-id 10;

Это простой тегированный интерфейс, на котором разрешен только 10 влан.
При добавлении интерфейсов, сконфигурированных таким методом в bridge-домен, в конфигурации самого bridge-домена необходимо явно указать данный интерфейс:
[edit]
bormoglotx@RZN-PE1# show bridge-domains BRIDGE-10 
domain-type bridge;
vlan-id 10;
interface ae3.10;

Для манипуляции с влан тегами можно использовать vlan-maps: input-vlan-map и output-vlan-map. Для примера перепишем тег 10 на тег 20:
[edit]
bormoglotx@RZN-PE1# show interfaces ae3.10 
encapsulation vlan-bridge;
vlan-id 10;
input-vlan-map {
    swap;
    vlan-id 20;
}
output-vlan-map swap;

При использовании vlan-map не забывайте добавлять обратное действие — то есть если вы укажете только input-vlan-map и забудете про output-vlan-map, то получите, что на прием тег будет переписываться, а на передачу нет.
Примечание: при использовании в bridge-доменах интерфейсов, сконфигурированные в Service Provider стиле с переписыванием тегов нельзя указать vlan-id, вы получите ошибку:
[edit]
bormoglotx@RZN-PE1# show bridge-domains 
BRIDGE-10 {
    domain-type bridge;
    vlan-id 10;
    ##
    ## Warning: interface with input/output vlan-maps cannot be added to a routing-instance with a vlan-id/vlan-tags configured
    ##
    interface ae3.10;

Если вы решили применять vlan-map вам придется удалить vlan-id из bridge-домена и управлять номерами тегов на каждом интерфейсе, входящим в данный bridge-домен вручную. Но есть и более простой способ переписывания тегов, который мы рассмотрим чуть позже.

Enterprise:

Данный метод намного проще предыдущего. При конфигурировании интерфейса мы указываем что это транковый или акссесный интерфейс. Исходя из этого мы можем указать несколько тегов (если интерфейс транковый) или один (если интерфейс акссесный). Но за простоту придется заплатить например следующими недостатками — отсутствует возможность произвести перезапись внутреннего тега при QinQ — вам доступны операции только над внешним тегом или например обязательное ручное переписывание тега, если он не соответствует vlan-id в bridge-домене, в отличи от Service Provider модели. Конфигурация выглядит так:

[edit]
bormoglotx@RZN-PE1# show interfaces ae0.10    
encapsulation vlan-bridge;
family bridge {
    interface-mode trunk;
    vlan-id-list 10;
}

В списке вланов vlan-id-list может быть указано больше одного номера влана — вплоть до 4094. Данный интерфейс не надо добавлять в bridge-домен — он будет добавлен в него автоматически, если совпадет номер влана в bridge-домене и на сконфигурированном интерфейсе. Вот например конфигурация bridge-домена с vlan-id 10:
bormoglotx@RZN-PE1> show configuration bridge-domains BRIDGE-10     
domain-type bridge;
vlan-id 10;
interface ae3.10;

Интерфейс ae0.10 суда не добавлен (если вы попытаетесь это сделать — JunOS выдаст вам ошибку). А теперь посмотрим состояние нашего bridge-домена:
bormoglotx@RZN-PE1> show bridge domain BRIDGE-10 

Routing instance        Bridge domain            VLAN ID     Interfaces
default-switch          BRIDGE-10                10       
                                                     ae0.10
                                                     ae3.10

Интерфейс был автоматически добавлен в bridge-домен, так как его номер влана равен сконфигурированному vlan-id в bridge-домене BRIDGE-10. Если во vlan-id-list будет несколько вланов, то интерфейс тоже будет добавлен в несколько bridge-доменов

Данный метод конфигурирования интерфейса тоже позволяет манипулировать влан тегами, правда конфигурация на первый взгляд может показаться несколько запутанной.

Сделаем тоже, что и с интерфейсом ae3.10 — перепишем тег с 10 на 20:

[edit]
bormoglotx@RZN-PE1# show interfaces ae0.10 
encapsulation vlan-bridge;
family bridge {
    interface-mode trunk;
    vlan-id-list 20;
    vlan-rewrite {
        translate 10 20;
    }
}

Обратите внимание, что номер влана, с которым пакет будет прилетать от клиента во vlan-id-list не указывается (если укажите, JunOS выдаст ошибку). Но важнее всего запомнить, что теперь это интерфейс с vlan-id 20 а не 10, и добавляться он будет в bridge-домен с vlan-id 20, а не 10. Если у вас будет несколько вланов на одном интерфейсе (это все таки транковый интерфейс), то вы можете написать несколько правил трансляции — для каждого влана отдельно. Для тех вланов, для которых правило трансляции отсутствует — тег будет оставаться тот, с каким он прилетел от клиента (естественно если данный влан разрешен на данном интерфейсе).

Выше я показал, что данный интерфейс был автоматически прикреплен к bridge-домену BRIDGE-10 так как их номера вланов совпали. Давайте проверим теперь — остался ли данный интерфейс в данном bridge-домене или же был перемещен:

bormoglotx@RZN-PE1> show bridge domain BRIDGE-10 

Routing instance        Bridge domain            VLAN ID     Interfaces
default-switch          BRIDGE-10                10       
                                                     ae3.10

Интерфейс был удален из bridge-домена BRIDGE-10, так как теперь у них не соответствуют номера вланов. Давайте сменим номер влана в данном bridge-домене на 20 и проверим еще раз, добавится ли теперь ae0.10:
bormoglotx@RZN-PE1> show bridge domain BRIDGE-10    

Routing instance        Bridge domain            VLAN ID     Interfaces
default-switch          BRIDGE-10                20       
                                                     ae0.10
                                                     ae3.10

Как и положено, интерфейс попал в данный bridge-домен.
Примечание: мы сменили номер влана в самом bridge-домене, но вот на ae3.10 номер влана не менялся, а он все так же в bridge-домене. Это нормально и как это работает мы рассмотрим чуть позже.

Bridge-domain

Так как с методами конфигурирования интерфейсов мы разобрались, теперь перейдем непосредственно к bridge-доменам.

Bridge-домен — это совокупность логических интерфейсов, которые имеют одинаковые характеристики изучения MAC-адресов и флудинга. Bridge-домен является синонимом слова широковещательный домен. Для примера представим, что мы создали bridge-домен, состоящий из 3-х интерфейсов. При получении широковещательного кадра маршрутизатор будет флудить его во все интерфейсы, кроме того, из которого этот кадр получен (как вы понимаете это функция split-horizon), то есть в нашем случае если пакет получен из интерфейса А, то он будет отправлен в интерфейс В и С. Больше никуда маршрутизатор пакет не отправит, что и обеспечивает изоляцию одного bridge-домена от другого. В итоге мы и получаем то определение, которое написано в начале данного абзаца — изучение MAC адресов и флуд ограничены только набором интерфейсов, которые прикреплены к данному bridge-домену. При определенных обстоятельствах влан будет являться синонимом bridge-домена, но это скорее исключение нежели правило.

Создадим простой bridge-домен — по сути прокинем влан 100 между RZN-PE1 и RZN-PE2, как это изображено на схеме ниже:

Так как в данном случае конфигурация bridge-домена на обеих РЕ-ках идентична, то выводы буду приводить только с первой коробки. Для начала проверим, что сейчас у нас нет связности между RZN-CE1 и RZN-CE2 во влане 100:

bormoglotx@RZN-CE1> show configuration interfaces ae0.100 
vlan-id 100;
family inet {
    address 10.0.0.1/24;
}

bormoglotx@RZN-CE1> ping rapid routing-instance VR1 source 10.0.0.1 10.0.0.2                      
PING 10.0.0.2 (10.0.0.2): 56 data bytes
.....
--- 10.0.0.2 ping statistics ---
5 packets transmitted, 0 packets received, 100% packet loss

Теперь перейдем на RZN-PE1 и добавим нужные нам интерфейсы и объединим их в bridge-домен:
[edit]
bormoglotx@RZN-PE1# show | compare 
[edit interfaces ae0]
+    unit 100 {
+        description "L2 for BRIDGE100";
+        encapsulation vlan-bridge;
+        vlan-id 100;
+    }
[edit interfaces ae3]
+    unit 100 {
+        description "to VR1";
+        encapsulation vlan-bridge;
+        vlan-id 100;
+    }
[edit]
+  bridge-domains {
+      BRIDGE-100 {
+          domain-type bridge;
+          vlan-id 100;
+          interface ae0.100;
+          interface ae3.100;
+      }
+  }

Как вы помните, на RZN-PE1 интерфейс ae0 смотрит в своего брата RZN-PE2, а ae3 в сторону RZN-CE1. Аналогичный конфиг применяется и на RZN-PE2. Теперь проверим состояние нашего bridge-домена:
bormoglotx@RZN-PE1> show bridge domain BRIDGE-100 detail 

Routing instance: default-switch
Bridge domain: BRIDGE-100                     State: Active
Bridge VLAN ID: 100                         
Interfaces:
    ae0.100
    ae3.100
Total MAC count: 0 

Все как мы планировали — в нашем домене два интерфейса и 100-й номер влана. Пока что ни одного MAC-адреса не изучено, так как между RZN-CE1/2 еще не было обмена трафиком. Исправим эту оплошность, запустив пинг между нужными нам адресами:
bormoglotx@RZN-CE1> ping rapid routing-instance VR1 source 10.0.0.1 10.0.0.2   
PING 10.0.0.2 (10.0.0.2): 56 data bytes
!!!!!
--- 10.0.0.2 ping statistics ---
5 packets transmitted, 5 packets received, 0% packet loss
round-trip min/avg/max/stddev = 3.451/6.046/13.350/3.674 ms

Теперь можем проверить, появились ли MAC-и в таблице форвардинга:
bormoglotx@RZN-PE1> show bridge domain BRIDGE-100 detail | match mac
Total MAC count: 2

bormoglotx@RZN-PE1> show bridge mac-table bridge-domain BRIDGE-100 

MAC flags (S -static MAC, D -dynamic MAC, L -locally learned, C -Control MAC
           SE -Statistics enabled, NM -Non configured MAC, R -Remote PE MAC)

Routing instance : default-switch
 Bridging domain : BRIDGE-100, VLAN : 100
   MAC                 MAC      Logical          NH     RTR
   address             flags    interface        Index  ID
   00:05:86:71:1c:c0   D        ae0.100         
   00:05:86:71:e5:c0   D        ae3.100  

Маки изучены, связность есть — все что надо — мы сделали. Хотелось бы сказать пару слов о том, что подобного результата можно было бы добиться и с помощью L2CKT. Но во-первых, L2CKT — это совершенно другой сервис, использующий MPLS как транспорт и не производящий изучение MAC-адресов (по сути труба от порта А до порта В), а во вторых, в L2CKT вы не сможете добавить третий интерфейс (придется делать VPLS), и самое главное вы не сможете прикрутить к L2CKT роутинговый интерфейс (irb) и выпустить хосты из вашего bridge-домена во внешнюю сеть. Для наглядности добавим irb интерфейс в наш bridge-домен, как это изображено на схеме:

bormoglotx@RZN-PE1# show | compare 
[edit interfaces]
+   irb {
+       unit 100 {
+           description "L3 for BRIDGE-100";
+           family inet {
+               address 10.0.0.254/24;
+           }
+       }
+   }
[edit routing-instances]
+   VIRTUAL-ROUTER {
+       instance-type virtual-router;
+       interface irb.100;
+   }
[edit bridge-domains BRIDGE-100]
+   routing-interface irb.100;

Так как внутри лабы между MX-сами тоже сеть 10.0.0.0/24, то я переместил irb интерфейс в виртуальный роутер. Если бы адресация у нас не пересекалась, то можно было бы оставить данный интерфейс в GRT и тем самым выпустить хосты из нашего L2 домена во внешний мир.
Примечание: irb интерфейс не является тегированный, номер юнита выставлен в 100 для удобства администрирования. При отправке трафика в роутинговый интерфейс, с пакета снимается влан тег, если таковой присутствует.

Примечание: irb интерфейс будет активен только тогда, когда будет добавлен в bridge-домен с хотя бы одним логическим интерфейсом, находящимся в состоянии up.

Примечание: в один bridge-домен можно добавить только один роутинговый интерфейс.

Теперь запустим пинг например с RZN-CE2 на 10.0.0.254 и проверим работоспособность нашей конфигурации:
bormoglotx@RZN-CE2> ping routing-instance VR1 rapid source 10.0.0.2 10.0.0.254    
PING 10.0.0.254 (10.0.0.254): 56 data bytes
!!!!!
--- 10.0.0.254 ping statistics ---
5 packets transmitted, 5 packets received, 0% packet loss
round-trip min/avg/max/stddev = 3.385/19.526/79.125/29.821 ms

Теперь немного усложним себе задачу и соберем вот такую схему:

В новом сценарии нам надо снова прокинуть L2 между нашими РЕ-ками, но вот проблема в том, что на RZN-PE1 терминируются 101 влан, а вот на RZN-PE2 — 1001 влан. Когда я писал, что при определенных условиях bridge-домен будет синонимом слова влан, то я имел ввиду случай, представленный выше (с 100-м вланом). Рассматриваемый же сейчас случай докажет вам то, что не всегда влан и bridge-домен одно и то же. Первое, о чем вы наверно подумали — что надо на каком то из интерфейсов настроить переписывание тегов, например из 1001 в 101 на RZN-PE2. Но сейчас я вам покажу, что JunOS позволит сделать нам это намного проще. Добавим на RZN-PE1 следующий конфиг:
[edit]
bormoglotx@RZN-PE1# show | compare 
[edit interfaces ae0]
+    unit 101 {
+        encapsulation vlan-bridge;
+        vlan-id 101;
+    }
[edit interfaces ae3]
+    unit 101 {
+        encapsulation vlan-bridge;
+        vlan-id 101;
+    }
[edit bridge-domains]
+   BRIDGE-101 {
+       domain-type bridge;
+       vlan-id 101;
+       interface ae0.101;
+       interface ae3.101;
+   }

Как результат применение данного конфига будет появление на RZN-PE1 bridge-домена BRIDGE-101:
bormoglotx@RZN-PE1> show bridge domain BRIDGE-101 

Routing instance        Bridge domain            VLAN ID     Interfaces
default-switch          BRIDGE-101               101      
                                                     ae0.101
                                                     ae3.101

Теперь перейдем к конфигурации со стороны RZN-PE2:
[edit]
bormoglotx@RZN-PE2# show | compare 
[edit interfaces ae0]
+    unit 101 {
+        encapsulation vlan-bridge;
+        vlan-id 101;
+    }
[edit interfaces ae3]
+    unit 1001 {
+        encapsulation vlan-bridge;
+        vlan-id 1001;
+    }
[edit bridge-domains]
+   BRIDGE-101 {
+       domain-type bridge;
+       vlan-id 101;
+       interface ae3.1001;
+       interface ae0.101;
+   }

В конфигурации никаких переписываний тегов нет, если посмотреть состояние bridge-домена, то четко видно, что интерфейс ae3.1001 находится в bridge-домене BRIDGE-101:
bormoglotx@RZN-PE2> show bridge domain BRIDGE-101 

Routing instance        Bridge domain            VLAN ID     Interfaces
default-switch          BRIDGE-101               101      
                                                     ae0.101
                                                     ae3.1001

По хорошему, так как у нас не сконфигрено переписывание тега, то ничего не заработает. А давайте проверим:
bormoglotx@RZN-CE1> ping rapid routing-instance VR1 source 11.0.0.1 11.0.0.2      
PING 11.0.0.2 (11.0.0.2): 56 data bytes
!!!!!
--- 11.0.0.2 ping statistics ---
5 packets transmitted, 5 packets received, 0% packet loss
round-trip min/avg/max/stddev = 5.033/7.733/13.904/3.206 ms

Уже интересно — связность есть, давайте проверим таблицу форвардинга на наличие MAC адресов:
bormoglotx@RZN-PE2> show bridge mac-table bridge-domain BRIDGE-101 

MAC flags (S -static MAC, D -dynamic MAC, L -locally learned, C -Control MAC
           SE -Statistics enabled, NM -Non configured MAC, R -Remote PE MAC)

Routing instance : default-switch
 Bridging domain : BRIDGE-101, VLAN : 101
   MAC                 MAC      Logical          NH     RTR
   address             flags    interface        Index  ID
   00:05:86:71:1c:c0   D        ae3.1001        
   00:05:86:71:e5:c0   D        ae0.101 

С MAC-ми в таблице форвардинга все в порядке. Но почему появилась связность между разными вланами без конфигурирования переписывания тега? Дело в том, что это дефолтное поведение JunOS. Вернемся к конфигурации самого bridge-домена BRIDGE-101 на RZN-PE2:
bormoglotx@RZN-PE2> show configuration bridge-domains BRIDGE-101  
domain-type bridge;
vlan-id 101;
interface ae3.1001;
interface ae0.101;

Все дело в указанном нами номере влана: vlan-id 101. Это работает так: для пакетов, полученных в данном bridge-домене с тегом, не соответствующим тегу 101, JunOS автоматически производит перезапись тега. Это легко увидеть на самом интерфейсе:
bormoglotx@RZN-PE2> show interfaces ae3.1001 
  Logical interface ae3.1001 (Index 339) (SNMP ifIndex 580)
    Flags: Up SNMP-Traps 0x20004000
    VLAN-Tag [ 0x8100.1001 ] In(swap .101) Out(swap .1001) 
    Encapsulation: VLAN-Bridge
    Statistics        Packets        pps         Bytes          bps
    Bundle:
        Input :             6          0           556            0
        Output:             6          0           570            0
    Adaptive Statistics:
        Adaptive Adjusts:          0
        Adaptive Scans  :          0
        Adaptive Updates:          0
    Protocol bridge, MTU: 1522

Обращаю ваше внимание на строку со следующим содержанием:
    VLAN-Tag [ 0x8100.1001 ] In(swap .101) Out(swap .1001) 

Эта строка указывает нам на то, какие действия будут производится с тегом на приеме из данного интерфейса ( In(swap .101) — тег меняется на 101) и при передаче в данный интерфейс ( Out(swap .1001) — тег меняется на 1001). Для примера изменю конфиг bridge-домена:
[edit]
bormoglotx@RZN-PE2# show bridge-domains BRIDGE-101  
domain-type bridge;
vlan-id none;
interface ae3.1001;
interface ae0.101;

И снова посмотрим, что будет производиться с тегом на ae3.1001:
bormoglotx@RZN-PE2> show interfaces ae3.1001 | match vlan-tag 
    VLAN-Tag [ 0x8100.1001 ] In(pop) Out(push 0x0000.1001) 

Теперь на прием тег снимается (а не переписывается), а на передачу добавляется тег 1001. Но так как интерфейс в сторону RZN-PE1 у нас тоже тегированный, то манипуляции с номерами вланов в данном случае будут и на нем (только номер влана 101):
bormoglotx@RZN-PE2> show interfaces ae0.101 | match vlan-tag     
    VLAN-Tag [ 0x8100.101 ] In(pop) Out(push 0x0000.101) 

Думаю, что немного в bridge-доменах разобрались. Давайте теперь попробуем создать на RZN-PE1 еще один bridge-домен с влан тегом 100:
[edit]
bormoglotx@RZN-PE1# show bridge-domains       
BRIDGE-100 {
    domain-type bridge;
    vlan-id 100;
    interface ae0.100;
    interface ae3.100;
}
BRIDGE-101 {
    domain-type bridge;
    vlan-id 101;
    interface ae0.101;
    interface ae3.101;
}
VLAN100 {
    vlan-id 100;
}

Проверим, будет ли коммит-скрипт ругаться на новую конфигурацию:
[edit]
bormoglotx@RZN-PE1# commit check 
[edit bridge-domains]
  'VLAN100'
    l2ald: Duplicate vlan-id exists for bridge domain BRIDGE-100
[edit bridge-domains]
  Failed to parse bridge domain hierarchy completely
error: configuration check-out failed

При коммите мы получаем ошибку — данный влан уже используется в другом bridge домене. Как быть в такой ситуации?? Да, можно поставить none и все полетит, но это не решение проблемы, а костыль (хотя весь транспорт держится на костылях и затычках, но все же хочется более жизнеспособное решение). Есть более изящный способ — мы просто создадим еще одно пространство вланов, которое будет полностью изолировано от уже имеющегося. В этом то нам и поможет виртуальный свич.

Разберемся детальнее. У нас есть 4094 влана на один маршрутизатор. То есть по факту вы ограничены в создании bridge-доменов — максимум 4094 домена с номером влана. Сделать два домена с одним и тем же номером влана вам не позволит JunOS. Когда я показывал вам вывод о состоянии bridge-домена, вы наверняка заметили в этом выводе название routing-instance:

bormoglotx@RZN-PE1> show bridge domain BRIDGE-100 detail 

Routing instance: default-switch
Bridge domain: BRIDGE-100                     State: Active
Bridge VLAN ID: 100                         
Interfaces:
    ae0.100
    ae3.100
Total MAC count: 0 

Нам интересна эта строка Routing instance: default-switch. То есть даже если вы не создавали ни одной routing-instance с типом virtual-switch, то у вас все равно будет одна такая routing-instance, которая называется default-switch. Все bridge-домены, которые я сконфигурировал раннее добавлялись именно в эту instance. Когда вы создаете routing-instance с типом virtual-switch, то вы добавляете еще одно пространство из 4094 вланов, которые никак не пересекаются и не влияют на уже используемые номера вланов. То есть делая например два виртуальных свича вы делаете возможным в пределах одного маршрутизатора создать три bridge-домена с одним и тем же номером влана, причем все эти bridge-домены будут абсолютно изолированы друг от друга. Помимо этого, если ваша топология предусматривает наличие резервных путей, которые могут привести к образованию петель, то виртуальный свич позволяет вам запускать протоколы семейства stp, правда есть ограничение — добавить вы можете только сам интерфейс, а не его юниты. И если у вас будет два виртуальных свича, в которые вы захотите добавите один и тот же интерфейс но разные юниты, то JunOS вернет вам ошибку при добавлении интерфейса в stp. Чтобы воспользоваться stp в этом случае придется использовать routing-instance layer2-control и протокол mstp.

Но на этом возможности виртуального свича не заканчиваются. В предыдущих вариантах создания bridge-домена мы использовали прямой линк между RZN-PE1 и RZN-PE2, как L2 линк и организовывали связность с помощью него. Но это не очень хорошо сказывается на отказоустойчивости, так как обрыв указанного линка разобьет наш растянутый L2 домен на две части. Чтобы этого избежать, можно воспользоваться VPLS или EVPN, причем нам не придется делать отдельную routing-instance с типом vpls или evpn — в иерархии виртуального свича можно настроить порт VPLS/EVPN и использовать его как транковый порт для организации L2 связности с удаленной PE-кой. Далее мы сконфигурируем виртуальный свич и будем использовать исключительно VPLS для связности между PE-маршрутизаторами.

Теперь мы можем перейти к созданию routing-instance с типом виртуальный свич. Мы соберем три схемы, которые будут отличаться друг от друга и покажут основные возможности bridge-доменов.
1. Bridge-домен BRIDGE-2000
2. Bridge-домен BRIDGE-2001
3. Bridge-домены BRIDGE-302 и BRIDGE-502

В первой схеме будем использовать два влана: 200 и 2000 (на RZN-PE1 будем переписывать тег 200 на 2000), во второй схеме тоже два влана: 201 и 2001 (но тут воспользуемся Service Provider методом конфигурирования интерфейса и ничего переписывать не будем — JunOS это сделает за нас), ну а в третьей схеме будем использовать три влана, вручную перепишем теги и сконфигурируем разные bridge-домены между RZN-PE1 и RZN-PE2 (один в виртуальном свиче, второй в дефолтном).

BRIDGE-2000
Схема сервиcа представлена ниже.

Сконфигрируем интерфейс на стороне RZN-PE1:

bormoglotx@RZN-PE1# show interfaces ae3.200 
description "to VR2";
encapsulation vlan-bridge;
family bridge {
    interface-mode trunk;
    vlan-id-list 2000;
    vlan-rewrite {
        translate 200 2000;
    }
}

Как указано на схеме, мы делаем перезапись тега с 200 на 2000. На стороне RZN-PE2 конфигурация интерфейса выглядит проще:
[edit]
bormoglotx@RZN-PE2# show interfaces ae3.2000 
description "to VR2";
encapsulation vlan-bridge;
family bridge {
    interface-mode trunk;
    vlan-id-list 2000;
}

Теперь перейдем к созданию виртуального свича. Конфигурация будет примерна одинакова и на RZN-PE1 и на RZN-PE2, поэтому конфиг приведу только с первой РЕ-ки:
bormoglotx@RZN-PE1> show configuration routing-instances vSwitch-1 
instance-type virtual-switch;
interface ae3.200;
route-distinguisher 62.0.0.1:1;
vrf-target {
    import target:1:1;
    export target:1:1;
}
protocols {
    vpls {
        site-range 2;
        no-tunnel-services;
        site SITE1 {
            site-identifier 1;
        }
    }
}
bridge-domains {
    BRIDGE-2000 {
        vlan-id 2000;
    }
}

Примечание: в конфигурации routing-instance присутствуют значения RD и RT. Это не обязательный атрибут данной routing-instance, но я использовал vpls Kompella, поэтому RD/RT мне необходимы для того, чтобы VPLS работал. Если использовать например Мартини без автодискаверинга, то указывать RT/RD указывать не надо.

Теперь проверим состояние bridge-домена BRIDGE-2000:
bormoglotx@RZN-PE1> show bridge domain instance vSwitch-1 BRIDGE-2000 

Routing instance        Bridge domain            VLAN ID     Interfaces
vSwitch-1               BRIDGE-2000              2000     
                                                     ae3.200
                                                     lsi.1048832

Помимо того, что в bridge-домен автоматически был добавлен интерфейс ae3.200, сюда же был добавлен и lsi интерфейс, который и есть наш vpls порт:
Instance: vSwitch-1
  Local site: SITE1 (1)
    connection-site           Type  St     Time last up          # Up trans
    2                         rmt   Up     Feb 23 12:20:38 2017           1
      Remote PE: 62.0.0.2, Negotiated control-word: No
      Incoming label: 262146, Outgoing label: 262145
      Local interface: lsi.1048832, Status: Up, Encapsulation: VPLS
        Description: Intf - vpls vSwitch-1 local site 1 remote site 2

Теперь RZN-PE2 в данном bridge-домене нам доступен через VPLS порт, а не как было раннее через прямой линк. Проверим есть ли связность между нашими хостами и как обстоят дела с таблицей форвардинга:
bormoglotx@RZN-CE1> ping rapid routing-instance VR2 source 20.0.0.1 20.0.0.2    
PING 20.0.0.2 (20.0.0.2): 56 data bytes
!!!!!
--- 20.0.0.2 ping statistics ---
5 packets transmitted, 5 packets received, 0% packet loss
round-trip min/avg/max/stddev = 4.571/11.609/37.058/12.731 ms

bormoglotx@RZN-PE1> show bridge mac-table instance vSwitch-1 vlan-id 2000 

MAC flags (S -static MAC, D -dynamic MAC, L -locally learned, C -Control MAC
           SE -Statistics enabled, NM -Non configured MAC, R -Remote PE MAC)

Routing instance : vSwitch-1
 Bridging domain : BRIDGE-2000, VLAN : 2000
   MAC                 MAC      Logical          NH     RTR
   address             flags    interface        Index  ID
   00:05:86:71:1c:c0   D        lsi.1048832     
   00:05:86:71:e5:c0   D        ae3.200  

Все в порядке — схема работает. Обратите внимание, что как и в дефолтном свиче в сам bridge-домен интерфейсы не добавлены — их автоматически добавит JunOS, когда будет парсить конфигурацию (так как мы использовали Enterprise стиль конфигурирования). Наше дело в данном сценарии — верно указать какие именно интерфейсы относятся к виртуальному свичу и верно указать vlan-id в bridge-домене (ну или vlan-id-range).

BRIDGE-2001
Теперь соберем схему для влана 201 и 2001.

Еще один виртуальный свич я делать не буду — просто добавлю еще один bridge-домен в vSwitch-1.Как я и сказал, будем использовать Service Provider стиль в конфигурировании интерфейсов без переписывания тегов.

Примечание: внутри виртуального свича, а так же внутри bridge-домена вам никто не запрещает использовать интерфейсы, сконфигурированные с использованием разных методов — как вам проще — так и делайте, главное потом самому не запутаться в конфигурации.

Конфигурации интерфейсов выглядят очень просто. На RZN-PE1:
[edit]
bormoglotx@RZN-PE1# show interfaces ae3.201 
description "to VR2";
encapsulation vlan-bridge;
vlan-id 201;

Встречный интерфейс на стороне RZN-PE2:
[edit]
bormoglotx@RZN-PE2# show interfaces ae3.2001 
description "to VR2";
encapsulation vlan-bridge;
vlan-id 2001;

Теперь добавим интерфейсы в наш виртуальный свич:
[edit]
bormoglotx@RZN-PE2# show routing-instances vSwitch-1           
instance-type virtual-switch;
interface ae3.2000;
##
## Warning: Only interface with 'interface-mode' is allowed in a virtual-switch
##
interface ae3.2001;

Получили ошибку. Дело в том, что в виртуальный свич в иерархию интерфейсов (сейчас и далее имеется ввиду иерархия [edit routing-instances vSwitch-1 interface]) можно добавить только интерфейсы в режиме транка или акссеса, то есть интерфейс должен быть сконфигурирован в стиле Enterprise. Это еще одно отличие этих двух методов конфигурирования интерфейсов. Если у нас интерфейс сконфигурирован в стиле Enterprise, то мы его не можем указать в иерархии bridge-домена — интерфейс будет добавлять в bridge-домен по номеру влана. Поэтому при конфигурировании виртуального свича нам необходимо добавить в него все наши интерфейсы, которые находятся в режиме транка или аксесса — далее JunOS автоматически добавит их в нужный bridge-домен. А вот интерфейсы, сконфигурированные в стиле Service Provider необходимо указать в bridge-домене вручную. Поэтому, при конфигурировании виртуального свича такие интерфейсы в виртуальный свич в иерархию интерфейсов добавлять не надо — мы их сразу добавляем в нужный нам bridge-домен, после чего интерфейс автоматически будет привязан к виртуальному свичу, в котором находится этот bridge-домен.
[edit]
bormoglotx@RZN-PE2# show routing-instances vSwitch-1 
instance-type virtual-switch;
interface ae3.2000;
route-distinguisher 62.0.0.2:1;
vrf-target {
    import target:1:1;
    export target:1:1;
}
protocols {
    vpls {
        site-range 2;
        no-tunnel-services;
        site SITE2 {
            site-identifier 2;
        }
    }
}
bridge-domains {
    BRIDGE-2000 {
        vlan-id 2000;
    }
    BRIDGE-2001 {
        vlan-id 2001;
        interface ae3.2001;
    }                                   
}

Примечание: VPLS или EVPN порт является транковым портом и пропускает все вланы и будет добавлен во все bridge домены автоматически.

Конфигурация на RZN-PE1 аналогична RZN-PE2 (только другой интерфейс добавлен в bridge-домен), поэтому показывать ее я не буду. Проверим состояние bridge-доменов на обеих РЕ-ках:

bormoglotx@RZN-PE1> show bridge domain instance vSwitch-1 BRIDGE-2001 detail                      

Routing instance: vSwitch-1
Bridge domain: BRIDGE-2001                    State: Active
Bridge VLAN ID: 2001                        
Interfaces:
    ae3.201
    lsi.1048832
Total MAC count: 0 

bormoglotx@RZN-PE2>  show bridge domain instance vSwitch-1 BRIDGE-2001 detail               

Routing instance: vSwitch-1
Bridge domain: BRIDGE-2001                    State: Active
Bridge VLAN ID: 2001                        
Interfaces:
    ae3.2001
    lsi.1048832
Total MAC count: 0

Проверим, что на ae3.201 происходит перезапись тегов:
bormoglotx@RZN-PE1> show interfaces ae3.201 | match vlan-tag 
    VLAN-Tag [ 0x8100.201 ] In(swap .2001) Out(swap .201) 

И для проверки запустим пинг между нашими хостами и проверим таблицу форвардинга:
bormoglotx@RZN-CE1> ping rapid routing-instance VR2 source 21.0.0.1 21.0.0.2    
PING 21.0.0.2 (21.0.0.2): 56 data bytes
!!!!!
--- 21.0.0.2 ping statistics ---
5 packets transmitted, 5 packets received, 0% packet loss
round-trip min/avg/max/stddev = 7.026/9.563/15.398/3.000 ms

bormoglotx@RZN-PE1> show bridge mac-table instance vSwitch-1 vlan-id 2001 

MAC flags (S -static MAC, D -dynamic MAC, L -locally learned, C -Control MAC
           SE -Statistics enabled, NM -Non configured MAC, R -Remote PE MAC)

Routing instance : vSwitch-1
 Bridging domain : BRIDGE-2001, VLAN : 2001
   MAC                 MAC      Logical          NH     RTR
   address             flags    interface        Index  ID
   00:05:86:71:1c:c0   D        lsi.1048832     
   00:05:86:71:e5:c0   D        ae3.201 

Ну и для полноты картины добавим в наш виртуальный свич роутинговый интерфейс:
[edit]
bormoglotx@RZN-PE1# show | compare 
[edit interfaces irb]
+    unit 2001 {
+        description "L3 for BRIDGE-2001 | vSwitch-1";
+        family inet {
+            address 21.0.0.254/24;
+        }
+    }
[edit routing-instances vSwitch-1 bridge-domains BRIDGE-2001]
+     routing-interface irb.2001;

Примечание: роутинговый интерфейс добавляется сразу в нужный нам bridge-домен виртуального свича, в иерархию интерфейсов виртуального свича данный интерфейс добавлять не надо.

Теперь проверим доступность irb интерфеса с RZN-CE2:

bormoglotx@RZN-CE2> ping routing-instance VR2 rapid source 21.0.0.2 21.0.0.254                    
PING 21.0.0.254 (21.0.0.254): 56 data bytes
!!!!!
--- 21.0.0.254 ping statistics ---
5 packets transmitted, 5 packets received, 0% packet loss
round-trip min/avg/max/stddev = 3.369/10.704/36.307/12.813 ms

Связность есть, а в таблице форвардинга на RZN-PE2 появился MAC адрес irb интерфейса, который логично виден через VPLS порт:
bormoglotx@RZN-PE2> show bridge mac-table instance vSwitch-1    

MAC flags (S -static MAC, D -dynamic MAC, L -locally learned, C -Control MAC
           SE -Statistics enabled, NM -Non configured MAC, R -Remote PE MAC)

Routing instance : vSwitch-1
 Bridging domain : BRIDGE-2001, VLAN : 2001
   MAC                 MAC      Logical          NH     RTR
   address             flags    interface        Index  ID
   00:05:86:71:17:c0   D        ae3.2001        
   00:05:86:71:26:c0   D        lsi.1048576     
   00:05:86:71:8d:f0   D        lsi.1048576 

Для справки приведу MAC-адрес irb интерфейса с RZN-PE1:
bormoglotx@RZN-PE1> show interfaces irb | match current 
  Current address: 00:05:86:71:8d:f0, Hardware address: 00:05:86:71:8d:f0

BRIDGE-302>>>BRIDGE-502
И в заключении соберем схему, в которой будем производить перезапись тегов вручную:

Со стороны RZN-PE1 у нас bridge-домен DOMAIN-302, который находится в виртуальном свиче vSwitch-2:

[edit]
bormoglotx@RZN-PE1# show | compare 
[edit interfaces ae0]
+    unit 900 {
+        encapsulation vlan-bridge;
+        vlan-id 900;
+        input-vlan-map pop;
+        output-vlan-map push;
+    }
[edit interfaces ae3]
+    unit 302 {
+        encapsulation vlan-bridge;
+        vlan-id 302;
+        input-vlan-map pop;
+        output-vlan-map push;
+    }
[edit routing-instances]
+   vSwitch-2 {
+       instance-type virtual-switch;
+       bridge-domains {
+           DOMAIN-302 {
+               interface ae3.302;
+               interface ae0.900;
+           }
+       }
+   }

Из конфигурации видно, что на приеме будет производиться снятие тега, и, что логично, на передачу — навешивание тега — ничего сложного.
Со стороны RZN-PE2 у нас будет просто bridge-домен BRIDGE-502 без создания виртуального свича (то есть будет находиться в дефолтном свиче):
bormoglotx@RZN-PE2# show | compare 
[edit interfaces ae0]
+    unit 900 {
+        encapsulation vlan-bridge;
+        vlan-id 900;
+        input-vlan-map pop;
+        output-vlan-map push;
+    }
[edit interfaces ae3]
+    unit 502 {
+        encapsulation vlan-bridge;
+        vlan-id 502;
+        input-vlan-map pop;
+        output-vlan-map push;
+    }
[edit bridge-domains]
+   BRIDGE-502 {
+       domain-type bridge;
+       interface ae3.502;
+       interface ae0.900;
+   }

Естественно и тут мы снимаем при приеме, и навешиваем при передаче влан теги.
В конечном счете наши bridge-домены на PE1/2 имеют такое состояние:
bormoglotx@RZN-PE1> show bridge domain instance vSwitch-2    

Routing instance        Bridge domain            VLAN ID     Interfaces
vSwitch-2               DOMAIN-302               NA       
                                                     ae0.900
                                                     ae3.302

bormoglotx@RZN-PE2> show bridge domain BRIDGE-502   

Routing instance        Bridge domain            VLAN ID     Interfaces
default-switch          BRIDGE-502               NA       
                                                     ae0.900
                                                     ae3.502

Вместо vlan-id у нас стоит NA — так как vlan-id не мной определен, так как я вручную перисываю теги на интерфейсах и задать это значение не могу. Для примера информация о произведении манипуляций с влан тегами с RZN-PE1:
bormoglotx@RZN-PE1> show interfaces ae3.302 | match vlan-tag 
    VLAN-Tag [ 0x8100.302 ] In(pop) Out(push 0x0000.302) 

bormoglotx@RZN-PE1> show interfaces ae0.900 | match vlan-tag    
    VLAN-Tag [ 0x8100.900 ] In(pop) Out(push 0x0000.900)

Теперь запустим пинг между хостами и проверим, есть ли у нас связность:
bormoglotx@RZN-CE1> ping routing-instance VR3 source 32.0.0.1 32.0.0.2 rapid                    
PING 32.0.0.2 (32.0.0.2): 56 data bytes
!!!!!
--- 32.0.0.2 ping statistics ---
5 packets transmitted, 5 packets received, 0% packet loss
round-trip min/avg/max/stddev = 5.982/6.443/7.162/0.432 ms

В таблицах форвардинга появились MAC-адреса:
bormoglotx@RZN-PE1> show bridge mac-table instance vSwitch-2  

MAC flags (S -static MAC, D -dynamic MAC, L -locally learned, C -Control MAC
           SE -Statistics enabled, NM -Non configured MAC, R -Remote PE MAC)

Routing instance : vSwitch-2
 Bridging domain : DOMAIN-302, VLAN : NA
   MAC                 MAC      Logical          NH     RTR
   address             flags    interface        Index  ID
   00:05:86:71:56:c0   D        ae3.302         
   00:05:86:71:ed:c0   D        ae0.900 

Правда в таком bridge-домене (без указания vlan-id) есть очень важное ограничение — в данный bridge-домен нельзя добавить роутинговый интерфейс. При попытке это сделать вы получите ошибку:
[edit]
bormoglotx@RZN-PE1# commit 
[edit routing-instances vSwitch-2 bridge-domains DOMAIN-302 routing-interface]
  'routing-interface irb.302'
    routing-interface can be configured only under bridge-domain with 'vlan-id' or 'vlan-tags'
error: commit failed: (statements constraint check failed)

Это может стать очень большой проблемой если L2 домен сильно разрастется и вдруг понадобится его выпустить во внешний мир — слишком много конфига придется в таком случае переписать. Поэтому использовать последнюю модель не рекомендуется, я же ее собрал только для демонстрации возможности bridge-домена в JunOS.

Вот собственно и все, что я хотел рассказать. Пытался объяснить все как можно проще, надеюсь, что читателю все понятно. Если есть какие то дополнения/замечания — пишите поправим/добавим. Спасибо за внимание!

P.S> В статье не стал затрагивать некоторые важные темы, такие как несколько learning-доменов в одном bridge-домене (для неподготовленного человека это будет взрыв мозга), указание диапазона вланов для bridge-домена и тд. Эти темы хорошо описаны в книге Juniper MX-Series-Trio (книга на английском) — если вам интересна эта тема — то данная книга будет отличным учебным пособием.

Комментарии (0)

    Let's block ads! (Why?)

    Архитектура растущего проекта на примере ВКонтакте

    Алексей Акулович объясняет жизненный путь высоконагруженного проекта на PHP. Это — расшифровка Highload ++ 2016.

    Меня зовут Лёша, я пишу на PHP.

    К счастью, доклад не об этом. Доклад будет про ретроспективу развития сети — того, как проект развивался. Какие решения капитанские или весьма специфические для нашей нагрузки мы применяли, что можно использовать в других проектах, которые испытывают нагрузки.

    Начнём.

    О чём мы будем говорить


    Рассказать обо всём за один доклад невозможно, поэтому я выбрал темы, которые мне показались наиболее интересными. Это вопрос развития доступа к базам данных и их хранение, вопрос оптимизации PHP и к чему мы пришли в итоге, в конце будет несколько примеров, как мы боремся с уже имеющейся архитектурой возникающие на production.

    В качестве небольшого оффтопика: буквально месяц назад ВКонтакте исполнилось 10 лет, довольно круглая цифра, не для айтишников правда, Highload’у тоже лет 10. То, что доклад приняли в программу на такой годовщине довольно приятно.

    С чего всё начиналось


    Это не самая первоначальная схема, но к ней сеть пришла довольно быстро. С ростом нагрузки и популярности получился такой типичный Lime Stack, когда у нас есть фронты на Nginx'e, они обрабатывают запросы, отсылают их на Apache, который boot smod PHP, а те ходят в MySQL или Memcached, стандартный lamp. Начнем именно с него, как отправной точки.

    Итак, нагрузка выросла.

    Если нагрузка на Nginx'e, который не имеет никаких локальных данных требующих, чтобы повторный запрос пользователя пришел к той же самой машине с Nginx’ом, то есть нам их не хватило — мы ставим ещё машин и всё работает дальше.

    Если нагрузки не хватает текущему размеру кластера на Apache, то для проекта, который не использует локальные сессии, локальные кэши, данных требующих чтобы запрос пришёл на тот же Apache и тот же PHР — мы опять ставим больше машин и всё работает. Стандартная схема.

    В случае MySQL и Memcached который является внешним хранилищем данных, которые нужны всем Apache и т.д., просто так доставить ещё одну машину не получится, нужно сделать что-то более хитрое и умное.

    Итак, начнем с развития базы данных и как оно вообще масштабируется. Самый первый, простой, банальный способ, который можно использовать — это масштабирование вертикальное: просто берём более мощную железку, больше процессора, диска и на какой-то момент нам этого хватает. Пока железка является какой-то типичной конфигурации, мы можем её позволить себе по деньгам, по железу. Так бесконечно расти невозможно.

    Более правильный вариант, к которому все приходят — это масштабирование горизонтальное. Когда мы стараемся не делать более мощные железки, а мы делаем этих железок больше, возможно даже менее мощные, чем та исходная — мы размазываем нагрузку. Это требует изменений в коде, чтобы код учитывал, что данные хранятся не в одном месте, не в одной корзине, а они как-то размазаны на основе какого-то алгоритма.

    Итак, для того чтобы как-то пытаться горизонтально масштабироваться, нам нужно на уровне кода уменьшить связность данных и независимость их от места и способа хранения. Самые простые вещи – это отказ от внешних ключей, join’ов и чего-либо другого, требующего хранения в одном месте.

    Другой вариант, когда у нас несколько табличек не влезает на один сервер, мы разносим их на разные сервера — обычное решение. Это можно делать с гранулярностью до одной таблицы на сервер.

    Если нам этого мало, то мы делим сами таблички на части, шардируем их и каждый кусочек хранится на отдельной машине. Это требует наибольшей переработки кода, мы не можем даже выполнить банальный select таблички, нам нужно сделать select в каждом шарде из фрагмента этой таблицы по ключу и потом где-то их смержить, в промежуточном слое, либо в нашем коде.

    В этот момент становиться актуальным вопрос дальнейшего роста — если мы один раз увеличили наше количество серверов, скорее всего нам ещё понадобиться их наращивать. Если мы каждый раз будем просто увеличивать количество машин, скажем с 8 до 16, то при перебалансировке данных в этих шардах — скорее всего у вас будет огромное количество миграций данных между кусками движка, между MySQL. Чтобы избежать этого огромного, волнообразного, переливания данных между машинами, лучше сразу завести виртуальные шарды, то есть мы говорим, что у нас есть не 8 шардов, а скажем у нас их 8 тысяч, но при этом первая тысяча хранится на первом сервере, вторая тысяча на втором сервере и т.д.

    При необходимости увеличения количества шардов мы переносим не сразу 1000 или 500 из них, а можем начинать с одного маленького шарда. Перенесли шард, всё — он работает с новой машины, она уже немножко нагружена, другая немножко разгрузилась. Гранулярность этого переноса уже определяется проектом, как он может себе позволить. Если переносить сразу половину шардов, мы вернёмся к схеме обычной миграции — если это допустимо.

    Как бы мы ни шардировали наши движки — это реляционная база данных, она универсальная, классная, но у неё есть некий пик производительности и вещи, которые требуют больше производительности выносятся в кэширование. Про обычный кэш: «сходили в кэш нет, пошли в базу», не будем говорить.

    Перейдём к более интересным вещам, которые помогают нам справляться с нагрузками, именно в вопросе кэширования.

    Первый вариант — это задача, связанная с предварительным созданием кэша. Полезно в тех случаях, когда у нас какой-то код, может конкурентный или в большом количестве, идти за какими-то данным в базу одновременно. Скажем, человек опубликовал пост у себя на стене, сведения об этом уезжают в ленту его друзьям. Если мы просто это сделаем, то весь код, который формирует ленту, разом ринется в кэш, в кэше этого поста нет, код пошёл в базу, это не очень хорошо. Куча кода одновременно лезет в базу.

    Что мы можем сделать? Мы можем после создания поста — в базе, сразу же, создать запись в кэше, только после этого отправить сообщение в ленту, что пост появился. Код формирования ленты придёт в кэш – запись есть; в базу мы больше не идём, база не используется. В случае если в наших кэшах хватает памяти, они не перезапускаются. Получается, что мы вообще никогда не ходим в базу на чтение, всё берётся из кэша.

    Другой вариант снижения нагрузки с базы данных — это использование просроченных кэшей. Это либо синхронная обработка счётчиков, либо это данные, которые хранятся дольше. В чём смысл? Мы можем в каких-то случаях бизнес-логики отдать не самые свежие данные, но сэкономить на этом поход в базу. Скажем, аватарка пользователя — если он её обновил, мы можем получать кэш сразу, он обновится из базы, либо мы можем обновить её в фоне через несколько секунд, друзья несколько секунд видят старую аватарку, некритично совершенно, а запроса в базу нет.

    Третий вариант, связанный с ещё большими нагрузками. Представим, что у нас есть блок с друзьями в профиле. Если мы хотим получить аватарку и имя, то мы должны сходить 6 раз в базу и получить данные по каждому — если после этого похода, полученные данные просто сохраним в кэш, то они вымоются примерно в одно и то же время.

    Чтобы как-то уменьшить эту нагрузку, мы можем сохранять данные в кэш профилей не на одно и тоже время, а плюс минус несколько секунд. При этом: когда мы повторно придём, то скорее всего часть данных ещё будет в кэше. Мы размазываем нагрузку на время TTL, на тот диапазон случайности хранения.

    Еще один способ, под ещё большую нагрузку — это когда мы приходим в базу, а она не смогла. Если приходит повторный запрос — в кэше данных нет, мы снова идём в базу, которая перед этим не смогла, она опять не может, ей становится хуже от нашей паратитной нагрузки и она может помереть.

    Чтобы этого не происходило, мы можем позволить себе иногда сохранить в кэш флажок, что не нужно идти в базу. Запрос приходит в кэш, в кэше нет записи, но там есть флажок «не иди в базу» и код не идёт в базу, он сразу отрабатывает по ветви кода, как будто бы мы не смогли, при этом запрос в базу не уходит — никаких таймаутов, ожиданий и всё хорошо, если это позволяет ваша бизнес-логика. Не упасть, но отдать хотя бы какой-то ответ без особой нагрузки.

    В любом случае если в системе вводится кэширование, то это кэширование не встроено в систему перманентного хранения, то есть в систему базы данных. Возникает проблема валидации — если код написан неаккуратно, то у нас могут получатся старые данные с кэша, либо в кэше может быть что-то не то. Всё это требует более аккуратного подхода к программированию.

    Что делать, когда нагрузка растёт, а дальше шардироваться нельзя

    У проекта возникла ситуация, когда MySQL уже не справлялся, вплоть до падений. Memcached были перегружены так, что их приходилось рестартить большими кусками. Работало всё плохо, расти было некуда, других решений в тот момент просто не существовало — был 2007-2008 год.

    Каким путём пошёл проект именно с такой нагрузкой


    Было принятно решение переписывать Memcached, который не работает, на решение, которое хотя бы будет выдерживать такую нагрузку, а затем пошло-поехало. Появились движки, связанные с targeting’ом и прочие вещи. Мы переносим нагрузку с универсального суперрешения, но которое работает не очень быстро, ввиду свой универсальности, на небольшие узкозаточенные решения, который делают маленький функционал, но делают его лучше, чем универсальные решения. При этом эти решения заточены именно под то использование, под ту нагрузку и типы запросов, которые используются на проекте.

    Это небольшой список движков, которые используются сейчас:

    Реально их больше, есть движки работающие с очередями, со списками, с лентой, куча разных движков. Допустим, движок lists занимается обработкой списков чего-либо. Он больше ничего не умеет, он работает именно под списки, но делает это хорошо.

    Как это выглядело?

    В тот момент было два коннектера к внешним данным. Был коннектор к Memcached, был коннектор к MySQL. Для протокола взаимодействия с движками был выбран протокол Memcached, движки прикидывались Memcached. При этом уже всё было шардировано, группы шардов одного типа объединялись в кластер. Доступ к кластеру как единому целому, на уровне бизнес-логики кода.

    Выглядело это примерно вот так:

    То есть «дай мне коннект к кластеру с таким именем», дальше мы просто используем коннект в каких-то своих запросах.

    Если на той стороне, на уровне кластеров и движков обычно Memcached, либо переделанный свой движок, прикидывающийся Memcached, то запрос выглядит вот так:

    Стандартное решение, обычный кэш.

    В случае, когда там более специфическое решение, требующее своего протокола, то приходилось городить такой вот огород:

    Мы не просто делаем plain ключ, а мы в ключ зашиваем дополнительные параметры, которые движок уже на своей стороне может распарсить и использовать как дополнительные параметры запроса. Это требовалось для того чтобы поддержать стандартный протокол и ничего не переделывать, и при этом движок мог принимать какие-то дополнительные параметры.

    В случае ещё более сложных движков, которым требуется большое количество параметров, запрос мог выглядеть вот так:

    Это уже сложный запрос — тут есть специальные символы-разделители, опциональные блоки. Но это всё работало, вполне себе хорошо. Если это писать не руками, а использовать какую-то обёртку для формирования такого запроса, то довольно неплохо.

    Как это выглядело?

    Код не ходят напрямую в кластер. Не требуется хранить топологию именно в коде, этим занимались прокси — это были специальные движки, которые работали на каждом сервере с кодом. То есть все коннекты кода были в локальный движок, а прокси уже настроены администраторами с учётом топологии хранения кластеров, она могла меняться на ходу. Нам неважно было где находится кластер, сколько в нём кусков, какие работы с ним проводятся — всё это неважно. Мы подключаемся к прокси, а дальше она делает свою работу. При этом один кластер мог пойти в другой кластер через прокси, если ему требовались какие-то внешние данные.

    Как выбирался шард в кластере


    Тут два варианта: мы либо выбираем на основе хеширования ключа целиком, либо мы ищем первое число, не цифру, а именно число в строке ключа, используем её как ключ для дальнейшей операции — взятие остатка по модулю. Стандартное более-менее решение.

    Популярное решение, работающее примерным образом — это twemproxy от Twitter. Если кто-то не пробовал, но при этом использует большие инсталляции Memcached или Redis, можете посмотреть, может вам пригодится.

    Всё это развивалось, росло, но в какой-то момент ограничение протокола возникло в виде конечного количества команд, в которое нас пытались впихнуть. Эти сложные запросы с кучей параметров — становилось довольно тесно в них, было еще ограничение по длине ключа 250 байт, а там куча параметров: строковые, числовые. Это было не очень комфортно, так же было ограничение текстового протокола, бинарного даже не было. Он ограничивал размер ответа в мегабайтах, так и обуславливал необходимость экранирования бинарных данных: пробелов, переносов строк и всего прочего.

    Это привело к тому, что решили мигрировать на бинарный протокол.

    Самый ближайший аналог это protobuf. Это протокол с заранее описанной схемой. Это не schemaless протоколы, вроде msgpack и подобных.
    Заранее записывается в схему, она хранится как в прокси — в конфиге. Можно выполнять запросы на его основе. Движки начали постепенную миграцию на эту схему. Кроме вопросов, связанных с решением проблем Memcached протокола. Были получены полезные плюшки с этого — например, перешли с TCP на UDP протокол, это сильно сэкономило серверам количество коннектов. Если поднимается движок, а к нему стучатся 300 тысяч коннектов и держатся постоянно, это не очень прикольно. Гораздо приятнее, когда это UDP пакетики и всё работает гораздо лучше. Шифрование соединений между машинами, что лично для меня самая приятная вещь — это асинхронное взаимодействие.

    Как работает, вообще, обычный запрос в том же PHP и многих других языках?

    Мы отсылаем запрос на внешнюю базу и ждём, блокируя worker, поток или процесс, в зависимости от реализации. В данном случае мы отсылаем запрос в локальную прокси — это очень быстро, и дальше работаем. Поток выполнений не блокируется, прокси сама, асинхронно, для нашего кода идёт в другой кластер, ждёт от него ответа, получает его и сохраняет в локальной памяти на какое-то время.

    Когда нам в коде понадобился ответ на формирования ответа — мы уже другие какие-то запросы сделали, тоже их подождали, выполнили. Мы идём в прокси: «дай нам ответ вот на тот запрос», она из локальной памяти просто нам её копирует и всё работает быстро и асинхронно. Это позволяет писать на однопоточном PHP довольно распараллеленный код.

    Остались ли у нас MySQL после миграции на движки? Движков уже много разных. Кое-где остались, их довольно мало, они в основном используются в вещах, связанных с внутренними админками, с интерфейсами без нагрузки, которые недоступны извне. Оно работает – не трогай, всех устраивает. Не ломается и ладно.

    Есть вещи, которые ещё, к сожалению, не переписаны — они не испытывают нагрузки, поэтому мы их перепишем когда-нибудь, когда будет время. Это приводит к названиям табличек, по типу таких:

    «Честное слово, это будет последняя табличка». Она была создана в том или этом году. Она нагрузки не испытывает, она есть, мы с этим живём и ладно. Хочется такие вещи сжечь, избавиться от них, как от пережитка, но MySQL у нас ещё есть.

    Теперь немножко поговорим о PHP


    У нас есть некая с движками, которая описана. Теперь, как мы жили в PHP.

    PHP – медленный, это не нужно никому объяснять, он имеет слабую динамическую типизацию.
    При этому он очень популярен. Проект создавался именно на этом языке, потому что он позволял очень быстро писать код — это плюс PHP. Размер кодовой базы уже такой, что переписывать не особо реально, мы живём с этим языком. Пользуемся его плюсами и пытаемся нивелировать минусы.

    Каждый проект пытается по-своему обойти ограничения PHP. Кто-то переписывает PHP, кто-то борется с самим PHP.

    Что пошло с нашей стороны


    Как на PHP обработать миллион запросов в секунду? Если у вас не 100500 серверов. Никак.
    Это практически невозможно, если нет какого-то бесконечного количества железа.

    Проект ВКонтакте пошёл по развитию нагрузок — был написан транслятор из PHP в C++, который переводит весь код сайта в сишный код. При этом что он делает? Он реализует не всю поддержку PHP, а именно тот уровень языка, который использовался на проекте в момент появления KPHP. Какие-то вещи появляются с тех пор, но в целом это работа на уровне былого кода. С тех пор мало что в нем появилось.

    Что делается? Ему транслируется весь код — есть статический анализатор, он пытается вывести типы, он анализирует использование переменных. Если переменные используются как строка всегда, то скорее всего в сишном коде std string. Если она используется как массив чисел, то это будет вектор int’ов. Это позволяет сишному компилятору очень хорошо оптимизировать полученный код.

    Приятная мелочь из вывода типов: транслятор, когда видит, что переменная используется совсем странно (она передаётся в функцию, где указан другой тип или передаётся в массив другого типа) — он может кинуть warning. Разработчик собирающий свой свежий код увидит, что вот тут какое-то подозрение — скорее всего стоить пойти и проверить, а точно ли там корректный код или это просто ложно срабатывание компилятора.

    Когда сайт переводился на KPHP было найдено большое количество ошибок за счёт статического анализа кода.

    Что получается? Мы получили из PHP кода — код С++, мы его собрали с дополнительными библиотеками, и имее обычный бинарник HTTP сервера. Он просто запускается за Nginx’ом как upstream и работает безо всяких дополнительных слоёв, обёрток, типа Apache и прочего.

    Ничего этого нет. Просто берётся машина, на ней запускается пачка процессов, там fork, но неважно — это просто работает.

    У нас есть специализированные движки, есть PHP код, который транслируется в KPHP движки.

    Как мы с этим живём (примеры из жизни)


    • Кто использует хранение конфигураций в обычных файлах ini, json, yml и прочее, есть такие?
    • А кто использует конфигурации в коде? Или использование кода на уровне хранения его в исходных кодах? Уже меньше.
    • А кто использует внешние системы хранения конфигураций, которые могут по запросу прислать тебе текущий конфиг? Больше.

    Наш вариант — это третий вариант. Используется внешние хранилище, как оно выглядит? Оно похоже на Memcached, у нас задействован протокол Memcached, при этом оно разделяется на master и slave.

    Master — это пишущие ноды, slave — это движки которые запущены на каждом сервере локально, где используется код. Код всегда, при получении конфига ходит в локальную реплику, на которой есть код — он распространяется с мастера на все slave. При этом в slave’ах есть защита от устаревания данных — в случае если там данные уже старые, то код не запустится, там могут важные вещи изменится в конфиге и работать лучше не стоит. Пусть лучше мы не выполнимся, чем выполнимся со старыми конфигами.

    Это позволяет масштабировать чтение конфига практически бесконечно, то есть получаем на каждый сервер свой личный конфиг. При этом высокая скорость распространения запросов — доли секунды по всем машинам, которые работают.

    Следующий вариант.

    Кто может сказать к чему может привести такой запрос: обычный запрос к Memcached, допустим он выполняется к сайту, у нес есть константный ключ, это не какая-то переменная, а одно и тоже. Есть идеи, что очень плохого может случиться? Ляжет весь сайт.

    Почему один банальный запрос, который попадет на каждый хит при большой нагрузке может всё сломать? Константный ключ, независимо от алгоритма шардирования, по хешу отключали — берём первое число. Мы так или иначе попадем на один и тот же шард того же самого кластера, весь объем запросов идёт на него. Движку становиться плохо, он или падает, или начнет тормозить. Нагрузка размазывается прокси на другие машины, но в целом становится плохо этому шарду, становится плохо прокси, которые копят очередь в него — у них таймауты, дополнительные ресурсы на обработку запросов этого шарда, становится плохо прокси и падает сайт, потому что прокси — это связующее звено всего сайта. Это сделать тяжело, но довольно таки неприятно.

    Что можно с этим сделать?

    Во-первых, не писать такой код, обычное решение.

    Но что делать, когда писать такой код нельзя, но хочется?

    Для этого используется шардирование ключа — для вещей связанных с подсчетом посещаемости, например — set инкременты каких-то счетчиков. Мы можем писать инкремент не в один счетчик, а размазать его на 10 тысяч ключей, при этом когда нам нужно получить значение счетчика, мы делаем multi get всех ключей и просто в нашем коде складываем. Мы размазали нагрузку на N-ое количество шардов, и мы хотя бы не упали.

    Если нагрузка еще больше и мы не можем еще больше масштабировать ключи, либо мы не хотим — нам не нужно суперточное значение, мы можем в инкременте использовать счетчики с вероятностью, например, 1/10 и при этом читая обратно значения, просто умножаем полученное значение на этот коэффициент вероятности и получаем близкое значение к тому, что мы бы получили если бы считали каждый отдельный хит. Мы размазали нагрузку на какое-то количество машин.

    Еще вариант — сайт начал тормозить.

    Видим, что выросла нагрузка на кластер общего Memcached, куча шардов которые кэшируют всё подряд. Что с этим делать? Кто виноват?

    В таком варианте неизвестно, потому что выросла нагрузка в целом на всё, на общий кластер — непонятно. Приходится заниматься reverse engineering коммитов, смотреть историю изменений.

    Что же здесь можно сделать? Можно разделить универсальные кластеры на конкретные маленькие шарды. Хочешь фотографии котика твоего другу, условно говоря, держи тебе один маленький кластер. Используй только его и не лазь в центральный, при этом когда растет нагрузка на этот кластер мы видим, что это именно он — мы знаем какой функционал нагрузил и при этом нагрузка на этот кластер совсем не влияет на все остальные. То есть даже если он целиком упадет, весь кластер, никто ничего особо не заметит в других кластерах.

    Другой вариант.

    По поводу предыдущего вопроса — по поводу «не упал». Вплоть до того, что если, скажем, падет какой-то раздел, например, сообщений, а человек смотрит видео с котиками. Если он не полезет отправить в личку этого котика другу, то он даже не узнает, что были какие-то проблемы. Он будет также успешно слушать музыку, смотреть ленту новостей и так далее. Он работает с той частью сайта, которая никак не зависит от того, что сломалось.

    Третий вариант.

    Допустим у нас есть 500-ые ошибки от PHP, то есть фронты Nginx’а получают от upstream’ов KPHP ошибки. Выкатили новый функционал, не оттестировали. Что делать?

    В случае если у нас есть общий кластер KPHP который обрабатывает весь сайт — непонятно.

    В случае если у нас есть разделение KPHP на кластеры: отдельно для API, отдельно для стены и прочее, можно подумать. Мы так же видим: «проблема вот с этим» и знаем какая группа методов, какие коммиты смотреть, а также кто виноват. При этом если это не массовая проблема, когда реально всё сломалось, а разовый случай — единичный сбой, то в Nginx есть возможность отправить запрос ещё раз, другому серверу KPHP. Скорее всего, если это была разовая проблема — повторный запрос пройдет и человек через какое-то время ожидания получит свой корректный и хорошо обработанный ответ.

    Выводы


    Каждый проект при своем росте переживает какую-то нагрузку и принимает те решения, которые в данный момент кажутся подходящими. Приходиться выбирать из имеющихся решений, если же решений нет, то приходится реализовывать свои — выходить за пределы стандартного, популярного используемого в сообществе подхода к борьбе с этими решениями. Но если в пределах проекта хорошо всё, всего хватает, то лучше использовать какие-то готовые решения, оттестированные, проверенные идеи сообщества. Если таких нет, то каждый решает сам.

    Смысл всего доклада — нужно её размазывать во всех частях вашего проекта.

    Всё, спасибо.

    Доклад: Архитектура растущего проекта на примере ВКонтакте.

    Комментарии (0)

      Let's block ads! (Why?)

      Технология JPEG: анализ пространства решений

      Сложно о простом: ESLint в команде

      Маленькое введение. Скорее всего этот пост будет интересен только тем, кто знает, что такое ESLint, но всё же сделаю небольшую вводную — а то сам сильно расстраиваюсь, когда открываю публикацию, и она начинается словами “уже 10 лет мы используем ххх, о котором вы конечно же знаете, а написать мы решили про xxx.yyy, что никто никогда не делал, но наверняка это очень круто”.

      Итак, ESLint это крутой инструмент, который позволяет проводить анализ качества вашего кода, написанного на любом выбранном стандарте JavaScript. Он приводит код к более-менее единому стилю, помогает избежать глупых ошибок, умеет автоматически исправлять многие из найденных проблем и отлично интегрируется со многими инструментами разработки (привет, Jetbrains, мы любим вас!). Кстати, он, как и другие линтеры, не обязывает вас к одному какому-то конкретному стилю. Наоборот — вы можете выбрать что-то из лучших практик и доработать по своему усмотрению!


      Среди применений можно найти довольно неожиданные кейсы — например, легаси код может быть легче прогнать автоматическим исправлением линтера, приведя к современным стандартам, в которых лучше видны ошибки, чем пытаться это править как есть. В результате вполне могут получиться невалидные конструкты — но по нашим наблюдениям это означает, что до преобразования там был просто отвратительный код.

      В общем, жить без линтера в Node.JS в 2017 году — это всё равно что писать код в notepad, при этом сидя на одной руке.

      А сегодня я расскажу вам, как мы решили его внедрить, чтобы эффективно работать с ним в команде, а не по одиночке.

      Большие ребята делают проверку линтером как часть процесса их CI, и мы до этого скоро дойдём. Но пока что нам нужно реализовать первичную необходимость — запуск линтера у разработчика, с гарантией, что всё взлетит и будет работать более-менее одинаково.

      Казалось бы — что такого, добавляешь .eslintrc.json в проект, и поехали! Однако возникает вопрос — а как, куда и кем должен ставиться ESLint и пачка необходимых для нашего code style плагинов? Обычно для этого используется три подхода:

      1. Давайте положим их в devDependency;
      2. Давайте никуда их не положим. Пускай у каждого будет глобально стоять mocha\eslint\прочее.
      3. Пускай всё ставить и прогонять проверки будут таск менеджеры вроде gulp или grunt.

      Второй вариант отметается сразу — думаю, понятно, что таким образом у нас пропадает версионирование самого инструмента от проекта к проекту, да и просто ставить глобально кучу инструментов и плагинов — совсем не круто.

      Третий вариант неплох, но до сих пор мы как-то обходились без таск менеджеров. Добавлять их в проекты ради такой задачи это явный overkill.

      Первый вариант в целом является оптимальным для проектов на гитхабе, но плохо подходит для коммерческой разработки. Наш CI предусматривает проведение автотестов на тестовых серверах, а тестовые зависимости прописать кроме devDependencies просто некуда. Но проблема не в этом, а в том, что, в отличие от автотестов, инструменты для линтера не должны попадать на тестовые сервера. Хотя бы по той причине, что тогда проект при раскатке резко начинает весить 200 с лишним мегабайт вместо 30. Кому-то это может показаться незначительным, но для соблюдения PCI DSS стандартов у нас повсеместно используется довольно серьёзное шифрование любой информации, так что раскатка обновления на 200 мегабайт занимает драгоценные минуты. Так что первый вариант нас тоже не устраивает. Подытожим:

      • Линтер и его плагины не должны стоять глобально;
      • Конкретный проект должен иметь привязки к конкретным версиям инструментов;
      • Эти инструменты не должны быть ни в dependencies, ни в devDependencies.

      На первый взгляд напрашивается простое решение — сделать npm скрипт, например, npm run lint-install, который будет дёргать баш скрипт, который ставит через командную строку все пакеты указанных версий. Но, помимо топорности такого решения, так же получается, что часть зависимостей (пусть и девелоперских) выезжает из package.json в некий отдельный баш скрипт… И это вот совсем не круто. Думаем дальше, вспоминаем спецификации package.json. В общем, никто нам не помешает добавить туда и свои какие угодно секции — но хотелось бы следовать неким стандартам.
      Из спецификации package.json вспоминаем, что есть такая довольно странная и редко используемая секция, как peerDependencies:

      In some cases, you want to express the compatibility of your package with a host tool or library, while not necessarily doing a require of this host. This is usually referred to as a plugin. Notably, your module may be exposing a specific interface, expected and specified by the host documentation.

      Автоматически они не ставятся, за исключением небольшого подводного камня:

      NOTE: npm versions 1 and 2 will automatically install peerDependencies if they are not explicitly depended upon higher in the dependency tree. In the next major version of npm (npm@3), this will no longer be the case. You will receive a warning that the peerDependency is not installed instead. The behavior in npms 1 & 2 was frequently confusing and could easily put you into dependency hell, a situation that npm is designed to avoid as much as possible.

      К счастью, npm у нас был уже не 2ой, так что можно было смело использовать данную секцию, не опасаясь внезапных последствий. Но проблема пришла откуда не ждали… Оказалось, что при удалении автоматической установки peerDependencies, авторы npm… Не сделали никакого способа поставить их вручную. Так что всё, что сейчас есть для секции peerDependencies — это предупреждение о том, что они не установлены. Отчасти эти объяснимо, поскольку зависимости эти опциональны, но всё же. У меня есть подозрение, что после такого изменения все разработчики просто перенесли всё в devDependencies… И dependency hell никуда не делся.
      Кстати, не одному мне отсутствие такой опции показалось странным. Есть даже issue по этому поводу — она закрыта, но помечена как patch-welcome. То есть авторы npm в целом согласны, что это косяк — просто у них не хватает времени на исправление…

      Итак, у нас теперь есть секция, но непонятно, как её использовать. 18 лайков той же самой issue есть на вот такое решение:

       npm info . peerDependencies | sed -n 's/^{\{0,1\}[[:space:]]*'\''\{0,1\}\([^:'\'']*\)'\''\{0,1\}:[[:space:]]'\''\([^'\'']*\).*$/\1@\2/p' | xargs npm i
      

      По-моему, ад. Как тимлид, я просто не могу позволить, чтобы такое пришло в наши проекты…

      В общем, дальше идея пошла в сторону написания или нахождения инструмента, который сам умеет парсить package.json и вызывать npm install — но не так жёстко, как скрипт, описанный выше. Более-менее менее меня удовлетворил npm-install-peers. Минуса у него два:

      1. Если он по какой-то причине не находит установленный в системе npm (через который его сейчас вообще-то ставят), то он… Ставит его заново локально, что вызывает расход времени, трафика, и иногда всякие адовые ошибки.
      2. Он не поддерживает какие бы то ни было аргументы. Если симлинки на windows уже можно включить, и --no-bin-links уже не очень актуален, то --production всё же хочется. Для тех же зависимостей линтера это бы сильно сэкономило время установки.

      Наверное, в близком будущем я просто сделаю аналогичный инсталлер, который делает то же самое, но дёргает npm не как модуль, а через bash. Пусть не так красиво, зато всё понятно с аргументами, и второй раз npm ставить не нужно. А аргументы передавать хочется — хотя бы чтобы не ставить devDependencies от eslint и его плагинов.

      Дальше встаёт вопрос — а как собственно глобально ставить npm-install-peers? Считать, что он есть по умолчанию? Ставить молча при выполнении скрипта? Ставить локально в devDependencies? Мне ни один из вариантов не понравился. В результате удовлетворился вот таким простым решением:

      "lint-install": "npm-install-peers || echo 'Please run npm install -g npm-install-peers first'",
      
      

      Такой вариант мне показался наиболее прозрачным для разработчика.

      И всё, что остаётся — добавить скрипт для запуска линтера и собственно нужные нам peerDependencies. Скрипт:

      
          "lint": "./node_modules/eslint/bin/eslint.js app.js routes modules test App.js"
      
      

      Зависимости:
      
      "peerDependencies": {
          "babel-cli": "^6.23.0",
          "babel-preset-es2015": "^6.22.0",
          "eslint": "^3.16.0",
          "eslint-config-airbnb": "^14.1.0",
          "eslint-plugin-import": "^2.2.0",
          "eslint-plugin-jsx-a11y": "^4.0.0",
          "eslint-plugin-promise": "^3.4.2",
          "eslint-plugin-react": "^6.10.0",
          "eslint-plugin-standard": "^2.0.1"
        }
      
      

      Кстати, как побочную фичу, мы теперь можем вынести в peerDependencies всякие прочие зависимости, которые не относятся к тестам — например, божественный jsdoc-to-markdown.

      Казалось бы — простая задача… Но всяких интересных нюансов оказалось довольно много. И я вполне допускаю, что можно было сделать проще и лучше. А как вы у себя используете линтеры для корпоративных проектов?

      Комментарии (0)

        Let's block ads! (Why?)

        Бэкапы как способ избежать лишних затрат при заражении криптовымогателем

        Из всех киберугроз, которые существуют на сегодняшний момент, ransomware можно назвать наиболее разрушительным. ПО такого типа быстро распространяется по сети (частной или корпоративной), закрывая доступ к данным, которые для человека или компании могут быть просто бесценными. Удалить криптовымогатель с ПК можно, но только со всеми данными, что есть на жестких дисках. Некоторые представители этого класса программного обеспечения не слишком опасны, и ключ или другие средства обезвреживания можно найти в сети. Но другие гораздо опаснее, если они уже попали в систему, то жертва не видит иного выхода, кроме как платить.

        Для оплаты чаще всего используются биткоины, хотя в некоторых случаях киберпреступники выбирают и другие способы оплаты. Платят очень многие. По данным ФБР, только в 2016 году различные компании выплатили разработчикам ransomware около $1 млрд. Это в 40 раз больше, чем аналогичный показатель, фигурировавший в 2015 году. Все потому, что ransomware становится все более совершенным, новые разработки киберпреступников появляются гораздо быстрее, чем раньше. Но все же — платить стоит далеко не всегда. Точнее, лучше бы вообще не платить.

        Стоит просто подумать над тем, как снизить вероятность попадания такого рода ПО в систему. Здесь, как всегда, можно порекомендовать несколько привычных вариантов, которые пригодятся как компаниям, так и обычным пользователям:

        • Обучить пользователей в своей компании основам кибербезопасности. Главное — не кликать по незнакомым ссылкам в e-mail сообщениях, что случается очень часто. Кроме того, пользователям стоит объяснить опасность посещения ресурсов, банеры которых часто попадаются в сети. Все мы знаем об этих бесплатных антивирусах и прочих штуках, теперь нужно объяснить это собственным сотрудникам.
        • Регулярно обновлять ПО в компании, также стоит обновлять прошивку сетевых устройств, следить за актуальностью ПО на серверах и прочих ключевых элементах сети.
        • Работать с файерволами, устанавливая последние обновления. В некоторых версиях такого ПО уже есть базы данных ресурсов с ransomware, так что пользователь, даже нажав на вредоносную ссылку, не попадет на такой сайт.
        • Использовать «песочницу» для полученных PDF-документов или документов других видов. Многие эксплоиты внедряются как раз в сложную структуру файлов, создаваемых при помощи офисных программ.
        • Разработать систему привилегий для пользователей в компании;
        • Если не требуется, всегда убирать функцию Remote Desktop Protocol.

        Собственно, все это подавляющее большинство читателей Хабра и так знает (многие даже пишут собственные книги или учебники на эту тему), но все же заново проверить свою систему безопасности в компании или дома не помешает.

        Роль безопасности


        Как бы там ни было, проблема не всегда в том, чтобы добавить мер безопасности. Слабое звено может найтись всегда. Не всегда помогают такие меры, как аудит, проверка целостности файлов, использование «отпечатков», серийных номеров и самовосстановление.

        Не менее важным элементом защиты данных своей компании или личных данных является разработка мер ликвидации последствий взлома. Очень немногие компании представляют, что будут делать после того, как беда произошла. Бегать, хватаясь за голову? Не вариант. Платить злоумышленникам? Да, этот способ используется гораздо чаще. Но это тоже не выход.

        image

        У каждой компании должен быть DRP-план (disaster-recovery plan), который поможет быстро выйти на предшествующий аварии рабочий уровень. Об этом мы уже писали. При разработке такого плана главное — проводить учебные тревоги с использованием полученной информации для ликвидации возможных брешей как в системе безопасности, так и в самом плане.

        И здесь стоит упомянуть еще один момент, о котором в прошлой статье мы не говорили. Это что? Правильно, бэкапы. Обязательно нужны копии файлов, с сортировкой версий по времени архивации. Просто бэкап не подходит — он может быть сделан в момент атаки и ничего хорошего накатывание такой версии архивных файлов не принесет.

        Поэтому, если данные для компании действительно важны, нужна надежная система архивации. Причем система архивации должна быть надежной — желательно такой, чтобы клиенты, даже с уровнем доступа типа «администратор» не могли ничего удалять из архивной копии. Поэтому даже, если зловредному ПО удастся забраться в сеть, то в архивной системе этот зловред ничего сделать не сможет.

        После заражения «здоровую» копию можно будет восстановить за несколько минут (или часов, в зависимости от масштаба) и снова получить здоровую систему. В подавляющем большинстве случаев пострадавшие компании, чьи данные стоят сотни тысяч или даже сотни миллионов долларов, не имели нормальной системы архивации, а если она и была, то ее работа проверялась лишь время от времени, а не на регулярной основе.

        Таким образом, один из возможных способов защиты файлов — закрыть свои бэкапы в надежном месте, и не давать никому ничего с ними сделать. Храниться они должны столько, сколько потребуется. Конечно, такие средства могут быть достаточно дорогими, но здесь уже стоит подсчитать возможные убытки в случае проникновения в сеть предприятия ransomware и подсчитать «овчинку» и «выделку».

        Надежных всем бэкапов!

        Комментарии (0)

          Let's block ads! (Why?)

          [Из песочницы] ASP.NET Core: ваше первое приложение на Linux c использованием Visual Studio Code

          пятница, 24 февраля 2017 г.

          Security Week 08: SHA-1 точно всё, уязвимости в роутерах TP-Link, кроссплатформенный ботнет с кодом Mirai

          Британские ученые доказали, что алгоритму криптографического хеширования SHA-1 точно настал конец. Как-то так вышло, что этой истории я кажется посвятил наибольшее количество упоминаний в дайджестах, возможно из-за того, что с криптографией не шутят: она либо работает, либо нет. Напомню, о чем идет речь: в конце 2015 года команда исследователей из университетов Голландии, Сингапура и Франции опубликовала доклад, в котором поделилась новыми идеями оптимизации алгоритма поиска коллизий при использовании SHA-1. По той оценке реальную атаку можно было провести примерно за 49 дней, потратив на облачные вычислительные мощности около 75000 долларов.

          Коллизия — это когда два разных объекта имеют один хеш. Если алгоритм SHA-1 используется для идентификации объекта, то появляется возможность «подсунуть» иной объект так, что «по документам» он будет идентичен оригиналу. И речь идет даже не о взломе шифрованной переписки, хотя SHA-1 по-прежнему довольно активно используется в криптографии. «Объекты» могут быть документами, сертификатами для идентификации определенного сервера: подмена в данном случае открывает широкий простор для кибератак.

          Но этот простор — теоретический: подтвердить уязвимость на практике стоит дорого. На этой неделе команда исследователей из Google и голландского CWI Institute сообщили, что они, таки да, смогли (новость, минисайт проекта).

          Результатом практической атаки на SHA-1 стало создание двух разных документов в формате PDF, у которых полностью совпадает хеш (пруф — 1,2). Создание документа потребовало огромного объема вычислений — более 9 квинтиллионов операций или 6500 лет процессорного времени (если этот сферический процессор будет трудиться в одиночку, конечно).

          Алгоритм поиска коллизий исследователи не раскрывают. Интересно, что в Google решили относиться к этой проблеме, как к любой другой уязвимости: через 90 дней обещают выложить исходный код атаки. Воспользоваться кодом на практике, понятное дело, смогут только очень состоятельные джентльмены. Хотя уже сейчас известно, что примененный метод в 100 тысяч раз быстрее брутфорс-атаки, повторный подход к снаряду обойдется, по мнению диванных аналитиков, в полмиллиона долларов в ценах Амазона. Если потерпеть и использовать только непиковую процессорную мощность, доступную со скидкой, получится около 110 тысяч долларов — в целом похоже на предсказанные полтора года назад $75k.

          Реальная атака с использованием коллизий известна в единственном экземпляре: Кибершпионская кампания Flame использовала данный прием для подписи вредоносного файла валидным (на тот момент) сертификатом Microsoft. Точнее, подпись была поддельная, но хеши у поддельной подписи и реальной совпадали. Но в том случае речь шла об алгоритме MD5, который «ломается» за секунды на любом оборудовании (но практическая атака все равно требует немало вычислительной мощности). С SHA-1 все несколько сложнее, но после практической реализации ничего интересного уже не произойдет. В ближайшем будущем обнаружат атаку, которая таки использует SHA-1 как один из многих способов взлома; это ненадолго привлечет внимание СМИ и сообщества. А еще лет через пять настанут трудовые будни админов, оставшихся один на один с устаревшей инфраструктурой, которую и выкинуть жалко, и использовать опасно, и заменить не на что.

          Google на странице проекта не забывает напомнить, что браузер Chrome помечает соединения с использованием SHA-1 как небезопасные. Это, конечно, здорово, но проблемы если и возникнут, то скорее всего не в браузере.

          Ждем ебилдов патчей для серьезных уязвимостей в роутерах TP-Link
          Новость. Подробности в гитхабе исследователя.

          Исследователь из Кот-д'Ивуара (!) Пьер Ким сообщил о множественных уязвимостях в роутерах TP-Link C2 и C20i (продаются в том числе и у нас), среди них — серьезная дыра в веб-интерфейсе, позволяющая выполнить на роутере произвольный код. Эксплуатируется уязвимость RCE достаточно просто: нужно залогиниться в веб-интерфейс, зайти на страницу диагностики и выполнить там любую команду, в том числе можно, например, запустить telnetd. Управлять роутером через telnet в дальнейшем можно вообще без авторизации.

          Как обычно, в этой истории интереснее не уязвимости (они не так чтобы ужасные — все же требуется логин для веб-интерфейса, но это преодолимо), а процесс починки вендором. К чести TP-Link, патч для уязвимостей вот-вот должен выйти. Но изначально исследователю сообщили в техподдержке, что контакта для информации о дырах в софте у них просто нет. Весь процесс, от обнаружения до (надеемся) патча занял у исследователя полгода.

          Исследователи «Лаборатории» обнаружили Windows-ботнет, атакующий IoT-устройства кодом Mirai
          Новость. Исследование.

          Ну как обнаружили. Ботнет, организованный кем-то, говорящим на китайском, существует уже довольно давно, но относительно недавно он начал использовать печально известный код атаки Mirai на уязвимые Linux-устройства. При этом сам ботнет построен из зараженных Windows-систем. В исследовании экспертов «Лаборатории» приводятся живописные примеры сбора вредоносной кампании, что называется, с миру по нитке. Атакующие слепили ботнет из того, что было: используются методы атаки на серверы MS SQL, вредоносный код подписывается украденными у ряда китайских компаний сертификатами, на пораженные системы внедряются кейлоггеры, все это построено чуть ли не на батниках.

          В общем это такой антипод опасных, но красивых, черт возьми, атак, который доказывает, что техника взлома чужих систем не обязана быть совершенной, чтобы быть эффективной. И так сойдет. Стать жертвой угрозы из говна и палок такого типа наверное вдвойне обидно. Mirai подключили к общему делу, видимо, по тем же принципам: прицепили где-то сбоку телнет-клиент и скрипты для массовой (или по спискам IP с командного сервера) бомбежки Linux-систем с прорехами в безопасности. Вишенка на тортике: вредоносный код прячется в хранящиеся на командном сервере картинки.

          Древности


          «Adolf-475»

          Резидентный опасный вирус, стандартно записывается в COM- и OVL-файлы при их загрузке в память. Свою резидентную копию помещает в таблицу векторов прерываний по адресу 0000:0020. Содержит текст: «Adolf Hitler». С вероятностью 1/8 блокирует удаление файлов. Перехватывает int 21h.

          Цитата по книге «Компьютерные вирусы в MS-DOS» Евгения Касперского. 1992 год. Страницa 58.

          Disclaimer: Данная колонка отражает лишь частное мнение ее автора. Оно может совпадать с позицией компании «Лаборатория Касперского», а может и не совпадать. Тут уж как повезет.

          Комментарии (0)

            Let's block ads! (Why?)

            MySQL и MongoDB — когда и что лучше использовать

            Петр Зайцев показывает разницу между MySQL и MongoDB. Это — расшифровка доклада с Highload++ 2016.

            Если посмотреть такой известный DB-Engines Ranking, то можно увидеть, что в течении многих лет популярность open source баз данных растет, а коммерческих — постепенно снижается.

            Что еще более интересно: если посмотреть на вот это отношение для разных типов баз данных, то видно, что для многих типов — таких, как колунарные базы данных, time series, document stories — open source базы данных наиболее популярны. Только для более старых технологий, таких как реляционные базы данных, или еще более древних, как multivalue база данных, коммерческие лицензии значительно популярнее.

            Мы видим, что для многих приложений используют несколько баз данных для того, чтобы задействовать их сильные стороны. Ни одна база данных не оптимизирована для всех всевозможных юзкейсов. Даже если это PostgreSQL [смех на сцене и в зале].

            С одной стороны, это хороший выбор, с другой — нужно пытаться найти баланс, так как чем у нас больше разных технологий, тем сложнее их поддерживать, особенно, если компания не очень большая.

            Часто что видим, что люди приходят на такие конференции, слушают Facebook или «Яндекс» и говорят: «Ух ты! Сколько вот люди делают интересного. У них технологий разных используется штук 20, и еще штук 10 они написали сами». А потом они тот же самый подход пытаются использовать в своем стартапе из 10 человек, что работает, разумеется, не очень хорошо. Это как раз тот случай, где размер имеет значение.

            Подходы к архитектуре


            Очень часто мы видим, что используется основное операционное хранилище и какие-то дополнительные сервисы. Например, для кэширования или полнотекстового поиска.

            Другой подход к архитектуре с использованием разных баз данных — это то, что используются микросервисы, где у каждого микросервиса может быть своя база данных, которая лучше оптимизирована для именно задач этого сервиса. Как пример, я говорил, может быть основное хранилище на MySQL, Redis/Memcache используется для кэширования. Мы используем Elastic Search и родной Sphinx для поиска. И что-то вроде Kafka может использоваться чтобы передавать данные в систему аналитики, которая часто делалась на чём-то вроде Hadoop.

            Если мы говорим про основное операционное хранилище, наверное, у нас есть два выбора. С одной стороны, мы можем выбрать реляционные базы данных, с языком SQL, с другой стороны, мы можем выбрать что-то нереляционное, дальше смотреть на подвиды, которые доступы в данном случае.

            Если говорить про NoSQL модели данных, то их тоже достаточно много. Наиболее типичные — это либо key value, либо document, либо wide column базы данных. Примеры: Memcache, MongoDB, Cassandra, соответственно.

            Почему в данном случае мы сравниваем именно MySQL и MongoDB? На самом деле причин несколько. Во-первых, если посмотреть на Ranking баз данных, то мы видим, что MySQL, по этому рейтингу является наиболее популярной реляционной базой данных, а MongoDB — это наиболее популярная нереляционная база данных. Поэтому их разумно сравнивать. С другой стороны, у меня есть наибольший опыт в использовании этих двух баз данных. Мы в Percona занимаемся плотно именно с ними, работаем с многими клиентами, помогаем им сделать такой выбор.

            Еще одна причина. Обе технологии изначально ориентированы на разработчиков простых приложений. Для тех людей, для которых PostgreSQL — это слишком сложно.

            Компания MongoDB изначально очень активно фокусировались на пользователях MySQL. Поэтому очень часто у людей есть опыт использования и выбор между этими двумя технологиями.

            В Percona мы кроме того, что занимаемся поддержкой, консалтингом для этих технологий, у нас есть достаточно много написанного open source софта для обоих этих технологий.

            На слайде можно посмотреть. Подробно я рассказывать об этом не буду.

            Что следует обо мне лично, я занимаюсь MySQL значительно больше, чем MongoDB. Несмотря на то, что я постараюсь предоставить сбалансированный обзор с моей стороны, у меня могут быть какие-то предрасположенности к MySQL, так как его тараканы я знаю лучше.

            Выбор MySQL и MongoDB


            Вот есть список разных вопросов, которые на мой взгляд имеет смысл рассматривать. Сейчас из них рассмотрим более детально.

            Что наиболее важно на мой взгляд — это учитывать, какие есть опыт и предпочтения команды. На самом деле для многих задач подходят оба решения. Их можно сделать и так, и так, может быть несколько сложнее, может быть несколько проще. Но если у вас команда, которая долго работала с SQL-базами данных и понимает реляционную алгебру и прочее, часто их перетягивать и заставлять использовать нереляционные базы данных, таких как MongoDB, где нет даже полноценной транзакции, может быть сложно.

            И наоборот: если есть какая-то команда, которая использует и хорошо знает MongoDB, SQL-язык может быть для неё сложен. Также имеет смысл рассматривать как оригинальную разработку, так и дальнейшее сопровождение и администрирование, поскольку всё это в итоге важно в цикле приложения.

            Какие есть преимущества у данных систем?

            Если говорить про MySQL — это проверенная технология. Понятно, что MySQL используется крупными компаниями более 15 лет. Так как он использует стандарт SQL, есть возможность достаточно простой миграции на другие SQL-базы данных, если захочется. Есть возможность транзакций. Поддерживаются сложные запросы, включая аналитику и так далее.

            С точки зрения MongoDB, здесь преимущество то, что у нас гибкий JSON-формат документов. Для некоторых задач и некоторым разработчикам это удобнее, чем мучиться с добавлением колонок в SQL-базах данных. Не нужно учить SQL. Простые запросы реже создают проблемы. Если посмотреть на проблемы производительности, в основном они возникают, когда люди пишут сложные запросы с JOIN в кучу таблиц и GROUP BY. Если такой функциональности в системе нет, то создать сложный запрос получается сложнее.

            В MongoDB встроена достаточно простая масштабируемость с использованием технологии шардинга. Сложные запросы если возникают, мы их обычно решаем на стороне приложения. То есть, если нам нужно сделать что-то вроде JOIN, мы можем сходить выбрать данные, потом сходить выбрать данные по ссылкам и затем их обработать на стороне приложения. Для людей, которые знают язык SQL, это выглядит как-то убого и ненатурально. Но на самом деле для многих разработчики application-серверов это куда проще, чем разбираться с JOIN.

            Подход к разработке и жизненный цикл приложений


            Следующий интересный момент. Если говорить про приложения, где используется MongoDB, и на чём они фокусируются — это очень быстрая разработка. Потому что всё можно постоянно менять, не нужно постоянно заботиться о строгом формате документа.

            Второй момент — это схема данных. Здесь нужно понимать, что у данных везде есть схема, нужно понять, где она реализуется. Вы можете реализовывать схему данных у себя в приложении, потому что каким-то же образом вы эти данные используете, либо это схема реализуется на уровне базы данных.

            Очень часто если у вас есть какое-то приложение, с данными в базе данных работает только это приложение. Например, мы сохраняем данные из этого приложения в эту базу данных. Схема на уровне приложения работает хорошо. Если у нас одни и те же данные используются многими приложениями, то часто это получается очень неудобно и сложно контролировать.

            Здесь возникает также вопрос времени жизни приложения. С MongoDB хорошо делать приложения, у которых очень ограниченный цикл жизни. То есть если мы делаем приложение, которое живёт недолго, например, сайт для запуска фильма или олимпиады, мы пожили несколько месяцев после этого, и это приложение практически не используется. Если приложение живёт дольше, то тут уже другой вопрос. Если говорить про распределение преимуществ и недостатков MySQL и MongoDB с точки зрения цикла разработки приложения, то их можно представить так:

            Если говорить про модель данных, то она очень сильно зависит от приложения и опыта команды. Сказать, что у нас реляционный или нереляционный подход к базам данных — это было бы странно.

            Если сравнивать их между собой, то понятно, что у нас есть. В MySQL — реляционная база данных. Мы можем с помощью реляционной базы данных отображать связи между таблицами. Нормализуя данные, мы можем заставлять изменения данных происходить атомарно в одном месте. Когда данные у нас денормализованы, нам не нужно при каких-то изменениях бежать и модифицировать кучу документов.

            Хорошо это или плохо? Результат — всегда таблица. С одной стороны, это просто, с другой — некоторые структуры данных не всегда хорошо ложатся на таблицу, нам может быть неудобно с этим работать.

            Это всё в теории. Если говорить о практическом использовании MySQL, мы знаем, что часто денормализуем данные, иногда для некоторых приложений мы используем что-то подобное: храним JSON, XML или другую структуру в колонках приложения.

            У MongoDB структура данных основана на документах. Данные многих веб-приложений отображать очень просто. Потому что если храним структуру — что-то вроде ассоциированного массива приложения, то это очень просто и понятно для разработчика сериализуется в JSON-документ. Как это всё раскладывать в реляционной базе данных по разным табличкам — это может быть несколько более нетривиально.

            Результаты как список документов, у которых может быть совершенно разная структура — более гибок.

            Как пример, мы хотим сохранить контакт-лист с телефона. Понятно, что у есть данные, которые хорошо кладутся в одну реляционную табличку: Фамилия, Имя и т.д. Если посмотреть на телефоны или email-адреса, то у одного человека их может быть несколько. Если подобное хранить в хорошем реляционном виде, то нам неплохо бы это хранить в отдельных таблицах, потом это всё собирать JOIN, что менее удобно, чем хранить это всё в одной коллекции, где находятся иерархические документы.

            Следует сказать, что это всё в строго реляционной теории — некоторые базы данных поддерживают массивы. В MySQL поддерживается формат JSON, в который можно засунуть такие вещи, как несколько email-адресов. Многие годы люди серилизовали это ручками: надо нам сохранить несколько email-адресов, то давайте запишем их через запятую, и дальше приложение разберётся. Но как-то это не очень хорошо.

            Термины


            Интересно, что между MySQL и MongoDB — вообще, между реляционными и нереляционными СУБД — что-то совпадает, что-то различается. Например, в обоих случаях мы говорим о базах данных, но то что мы называем таблицей в реляционной базе данных, часто в нереляционной называется коллекцией. То, что называется колонкой в MySQL, в MongoDB — поле. И так далее.

            С точки зрения использования JOIN, в MongoDB нет такого понятия — это вообще понятие из реляционной структуры. Там мы либо делаем встроенный документ, что близко к концепту денормализации, либо мы просто сохраняем идентификатор документа в каком-то поле, называем это ссылкой и дальше ручками выбираем данные, которые нам нужны.

            Что касается доступа: там, где мы к реляционным данным мы используем язык SQL, в MongoDB и многих других NoSQL-базах данных используется такой стандарт как CRUD. Этот стандарт говорит, что есть операции для создания, чтения, удаления и обновления документов.

            Несколько примеров.

            Как у нас могут выглядеть наиболее типичные задачи по работе с документами могут выглядеть в MySQL и MongoDB:

            Вот пример вставки.

            Пример обновления.

            Пример удаления.

            Если вы разработчик, который знаком с языком JavaScript, то такой синтаксис, который предоставляет CRUD (MongoDB), для вас будет более естественным, чем синтаксис SQL.

            На мой взгляд, когда у нас есть простейшие операции: поиск, вставка, они все работают достаточно хорошо. Когда речь идёт о более интересных операциях выборки, на мой взгляд, язык SQL куда более читаемый.

            &gt вместо простого знака «>». Не очень читаемо, на мой взгляд.

            Достаточно легко с помощью интерфейса делать такие вещи, как подсчёт числа строк в таблице или коллекции.

            Но если мы делаем более сложные вещи, например, GROUP BY, в MongoDB для этого требуется использовать Aggregation Framework. Это несколько более сложный интерфейс, который показывает, как мы хотим отфильтровать, как мы хотим группировать и т.д. В Aggregation Framework уже поддерживает что-то вроде операций JOIN.

            Следующий момент — это транзакции и консистентность (ACID). Если пойти и почитать документацию MongoDB, там будет: «Мы поддерживаем ACID-транзакции, но с ограничением». На мой взгляд, стоит сказать: «ACID мы не поддерживаем, но поддерживаем другие минимальные нетранзакционные гарантии».

            Какая у нас между ними разница?

            Если говорить про MySQL, он поддерживает ACID-транзакции произвольного размера. У нас есть атомарность этих транзакций, у нас есть мультиверсионность, можно выбирать уровень изоляции транзакций, который может начинаться с READ UNCOMMITED и заканчиваться SERIALIZABLE. На уровне узла и репликаций мы можем конфигурировать, как данные хранятся.

            Мы можем сконфигурировать у InnoDB, как работать с лог-файлом, сохранять его на диск при коммите транзакции или же делать это периодически. Мы можем сконфигурировать репликацию, включить, например, Semisynchronous Replication, когда у нас данные будут считаться сохранёнными только тогда, когда их копия будет на одном из slave’ов принята.

            MongoDB не поддерживает транзакции, но он поддерживает атомарные операции над документом. Это значит, что с точки зрения одного документа операция у нас будет атомарна. Если у нас операция изменяет несколько документов и произойдет какой-то сбой внутри во время этой операции, то какие-то из этих документов могут быть изменены, а какие-то — не изменены.

            Консистентность тоже делается на уровне документов. В кластере мы можем выбирать гибкую консистентность. Мы можем указать, какие мы хотим гарантии — гарантии, что у нас данные были записаны только на один узел, или они были реплицированы на все узлы кластеров. Чтение консистентности тоже происходит на уровне документа.

            Есть такой вариант обновления isolated, который позволяет выполнить обновление изолированно от других транзакций, но он очень неэффективен — он переключает базы данных в монопольный режим доступа, поэтому он используется достаточно редко. На мой взгляд, если говорить про транзакции и консистентность, то MongoDB достаточна убогая.

            Производительность


            Производительность очень сложно сравнивать напрямую, потому что мы часто делаем разные схемы базы данных, дизайн приложения. Но если говорить в целом, MongoDB изначально была сделана чтобы хорошо масштабироваться на много узлов через шардинг, поэтому эффективности было уделено меньше внимания.

            Это результаты бенчмарка, который делал Марк Каллаган. Здесь видно, что с точки зрения использования процессора, ввода/вывода, то MySQL как InnoDB, так и MyRocks, использует значительно меньше процессора и дискового ввода/вывода на операции бенчмарка Linkbench от Facebook.

            Масштабируемость.

            Что такое масштабируемость в данном контексте? Насколько легко нам взять наше маленькое приложение и масштабировать его на многие миллионы, может быть, даже на миллиарды пользователей. Масштабируемость бывает разная. Она бывает средняя, в рамках одной машины, когда мы хотим поддерживать приложения среднего размера, либо масштабируемость на кластере, когда у нас приложения уже очень большие, понятно, что даже одна самая мощная машина не справится.

            Также имеет смысл говорить о том, масштабируем ли мы чтение, запись ил объем данных. В разных приложениях их приоритеты могут различаться, но в целом, если приложение очень большое, обычно им приходится работать со всеми из этих вещей.

            В MySQL в новых версиях весьма хорошая масштабируемость в рамках одного узла для LTP-нагрузок. Если у нас маленькие транзакции, есть какая-нибудь железо, в котором 64 процессора, то масштабируется достаточно хорошо. Аналитика или сложные запросы масштабируются плохо, потому что MySQL может использовать для одного запроса только один поток, что плохо.

            Традиционно чтение в MySQL масштабируется с репликацией, запись и размер данных — через шардинг. Если смотреть на все большие компании — Facebook, Twitter — они все используют шардинг. Традиционно шардинг в MySQL используется вручную. Есть некоторые фреймворки для этого. Например, Vitess — это фреймворк, который Google использует для scaling сервиса YouTube, они его выпустили в open source. До этого был framework Jetpants. Стандартного решения для шардинга MySQL не предлагает, часто переход на шардинг требует внимания от разработчиков.

            В MongoDB фокус изначально был в масштабируемости на многих узлах. Даже в случаях с маленьким приложением, многим рекомендуется использовать шардинг с самого начала. Может, всего пару replica set, потом вы будете расти вместе со своим приложением.

            В шардинге MongoDB есть некоторые ограничения: не все операторы с ним работают, например, есть isolated-вариант для обеспечения консистентности. Она не работает если использовать шардинг. Но при этом многие основные операции хорошо работают в шардингом, поэтому людям позволяется scale’ить приложения значительно лучше. На мой взгляд, шардинг и вообще репликация в MongoDB сделаны куда лучше, чем MySQL, значительно проще в использовании для пользователя.

            Администрирование


            Администрирование – это все те вещи, о которых не думают разработчики. По крайней мере в первую очередь. Администрирование — это то, что нам приложение придётся бэкапить, обновлять версии, мониторить, восстановливать при сбоях и так далее.

            MySQL достаточно гибок, у него есть много разных подходов. Есть хорошие open source реализации всего, но это множество вариантов порождает сложность. Я часто общаюсь с пользователями, которые только начинают изучать MySQL. Они говорят: «Ёлки-палки, сколько же у вас всего вариантов. Вот только репликация — какую мне использовать: statement-репликацию, raw-репликацию, или mix? А еще есть gtid и стандартная репликация. Почему нельзя сказать „просто работай“?»

            В MongoDB всё больше ориентированно на то, что оно работает каким-то одним стандартным образом — есть минимизация администрирования, но понятно, что это происходит при потере гибкости. Коммьюнити open source решений для MongoDB значительно меньше. Многие вещи в MongoDB с точки зрения рекомендаций достаточно жестко привязаны к Ops Manager — коммерческой разработке MongoDB.

            Мифы


            Как в MongoDB, так и в MySQL есть мифы, которые были в прошлом, которые были исправлены, но у людей хорошая память, особенно если что-то не работает. Помню, в MySQL после того как появились транзакции с InnoDB, люди мне лет десять говорили: «А в MySQL нет же транзакций?»

            В MongoDB было много разных проблем с производительностью MMAP storage engine: гигантские блокировки, неэффективное использование дискового пространства. Сейчас стандартный движок WiredTiger многие проблем уже не имеет. Есть другие проблемы, но не эти.

            «Нет контроля схемы» — ещё такой миф. В новых версиях MongoDB можно для каждой коллекции определить на JSON структуру, где данные будут валидироваться. Данные, которые мы пытаемся вставить, и они не соответствуют какому-то формату, можно выкидывать.

            «Нет аналога JOIN» — то же самое. В MongoDB это появилось, но нескольких ограниченных вещах. Только на уровне одного шарда и только если мы используем Aggregation framework, а не в стандартных запросах.

            Какие у нас есть мифы в MySQL? Здесь я буду говорить больше о поддержке NoMySQL решений в MySQL, об этом я буду говорить завтра. Следует сказать, что MySQL сейчас тоже можно использовать через интерфейс CRUD’a, использовать в NoSQL режиме, примерно как MongoDB.

            Типичный пример, где используется MySQL-решение — это сайт электронной коммерции. Когда у нас идёт вопрос о деньгах, часто мы хотим полноценные транзакции и консистентность. Для таких вещей хорошо подходит реляционная структура, которая была проработана, и commerce на реляционных базах данных уже делается многие десятилетия. Так что можно взять один из готовых подходов к структуре данных и использовать его.

            Обычно с точки зрения e-commerce объем данных у нас не такой большой, так что даже достаточно большие магазины могут долгj работать без шардинга. Приложения у нас постоянно разрабатываются и усовершенствуется на протяжении многих лет. И у этого приложения много компонентов, которые работают с одними и теми же данными: кто-то рассчитывает, где цены поменять, кто-то ещё что-то делает.

            MongoDB часто используется как бэкенд больших онлайн-игр. Electronic Arts для очень многих игр используют MongoDB. Почему? Потому что масштабируемость очень важна. Если какая-то игра хорошо выстрелит, её часто приходится масштабировать значительно больше, чем предполагалось.

            С другой стороны, если не выстрелит, нам хотелось бы, чтобы инфраструктуру можно было бы уменьшить. Во многих играх это идет так: мы запустили игру, у нее есть какой-то пик, приходится делать большой кластер. Потом игра уже выходит из популярности, для неё бэкенд нужно сжимать, сохранять и использовать. Используется в данном случае одно приложение (игра), база данных, с одной стороны, несложная, с другой — сильно привязана к приложению, в котором хранятся все важные для игры параметры.

            Часто консистентность базы данных на уровне объектов здесь достаточна, потому что многие вопросы консистентости решаются на уровне приложения. Например, что данные одного игрока сохраняет только один application service.

            Дополнительная информация


            Всем рекомендую это старое, древнее, но очень смешное видео http://ift.tt/t9cBWv. На этом мы закончим.

            MySQL и MongoDB — когда что лучше использовать?

            Комментарии (1)

              Let's block ads! (Why?)