Разговор об алгоритме балансировки нагрузки и его реализации

Java

Помню, когда я только начал работать, один коллега рассказал историю: когда он только работал, его коллега однажды прибежал в компанию и сказал: «Вы знаете, компания наняла большого парня». большая корова? Да, этот человек может написать AJAX! Ничего себе, какой великий человек, вы можете многому научиться, следуя за ним. Я слушал и смеялся, но это немного сложно понять, потому что теперь почти до тех пор, пока разработчик будет писать AJAX, как вы можете писать AJAX, чтобы быть мастером? Позже я понял, что непредсказуемая технология трехлетней давности теперь стала обыденностью, что неудивительно.Так же, как балансировка нагрузки, о которой мы будем говорить сегодня, в геометрическом времени только большие коровы могут играть в балансировку нагрузки, но в этот день маленький разработчик может сказать несколько слов. Теперь давайте кратко рассмотрим балансировку нагрузки.

С точки зрения оборудования балансировки нагрузки оно делится на балансировку аппаратной нагрузки и балансировку нагрузки программного обеспечения:

  • Аппаратная балансировка нагрузки: Например, самые распространенные F5, и Array и т. д. Эти балансировщики нагрузки являются коммерческими балансировщиками нагрузки с более высокой производительностью.В конце концов, они рождены для балансировки нагрузки, и за ними тоже стоят очень зрелые команды. решение, но цена относительно высока, поэтому нет веской причины, и достаточное количество мягких сестринских монет не будет рассматриваться.
  • Балансировка нагрузки программного обеспечения: включает в себя знакомые нам Nginx, LVS, Tengine (выполнена трансформация Nginx Али) и так далее. Преимущество заключается в относительно низкой стоимости, но также необходимо иметь более профессиональную команду для обслуживания, она должна выйти на яму, заняться своими руками.

С точки зрения технологии балансировки нагрузки она делится на балансировку нагрузки сервера и балансировку нагрузки клиента:

  • Балансировка нагрузки на стороне сервера: когда мы обращаемся к сервису, запрос сначала отправляется на другой сервер, а затем этот сервер распределяет запрос на сервер, предоставляющий сервис.Конечно, если есть только один сервер, это легко сказать, отправить запрос прямо на этот сервер. Один сервер в порядке, но что, если серверов несколько? В это время сервер будет выбран по определенному алгоритму.
  • Балансировка нагрузки на стороне клиента: кажется, что концепция балансировки службы на стороне клиента родилась вместе с управлением службами.Короче говоря, она поддерживает IP-адрес, имя и другую информацию обо всех службах на одном сервере.Когда мы обращаемся к службе в коде Доступ к сервису осуществляется через компонент, который получает информацию обо всех серверах, предоставляющих услугу, с этого сервера, а затем выбирает сервер для запроса по определенному алгоритму.

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

случайный

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

Полностью случайно

public class Servers {
    public List<String> list = new ArrayList<>() {
        {
            add("192.168.1.1");
            add("192.168.1.2");
            add("192.168.1.3");
        }
    };
}
public class FullRandom {
    static Servers servers = new Servers();
    static Random random = new Random();

    public  static String  go() {
        var number = random.nextInt(servers.list.size());
        return servers.list.get(number);
    }

    public static void main(String[] args) {
        for (var i = 0; i < 15; i++) {
            System.out.println(go());
        }
    }
}

результат операции:image.pngХотя сейчас это не кажется таким случайным, некоторые серверы получаются часто, а некоторые серверы - реже, но когда будет достаточно запросов, они станут все более и более средними, что характерно для случайных чисел.

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

взвешенный случайный

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

Существует две реализации на взвешенном случайном алгоритме:

Один из них циркулирует в Интернете, код относительно проста: построить список серверов, если вес сервера A - 2, затем добавьте сервер A в список дважды, если вес сервера B составляет 7, то я добавляю 7 Время к спискому серверу B и так далее, а затем я генерирую случайное число. Верхний предел случайного числа является суммой весов, что составляет размер списка. Чем выше вес, тем выше вероятность выбранного. Код выглядит следующим образом:

public class Servers {
    public HashMap<String, Integer> map = new HashMap<>() {
        {
            put("192.168.1.1", 2);
            put("192.168.1.2", 7);
            put("192.168.1.3", 1);
        }
    };
}
public class WeightRandom {

    static Servers servers = new Servers();
    static Random random = new Random();

    public static String go() {
        var ipList = new ArrayList<String>();
        for (var item : servers.map.entrySet()) {
            for (var i = 0; i < item.getValue(); i++) {
                ipList.add(item.getKey());
            }
        }
        int allWeight = servers.map.values().stream().mapToInt(a -> a).sum();
        var number = random.nextInt(allWeight);
        return ipList.get(number);
    }

    public static void main(String[] args) {
        for (var i = 0; i < 15; i++) {
            System.out.println(go());
        }
    }
}

результат операции:image.png

Хорошо видно, что вероятность выбора сервера с малым весом относительно мала.

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

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

Так умные программисты придумали второй способ:

Для простоты объяснения возьмем приведенный выше пример:

Если вес сервера равен 2, вес сервера B равен 7, а вес сервера C равен 1:

  • Если случайное число, которое я генерирую, равно 1, то оно попадает на сервер A, потому что 1
  • Если случайное число, которое я генерирую, равно 5, то оно попадает на сервер B, потому что 5>2 (вес сервера A), 5-2 (вес сервера A) = 3, 3
  • Если случайное число, которое я генерирую, равно 10, то оно попадает на сервер C, потому что 10>2 (вес сервера A), 10-2 (вес сервера A) = 8, 8>7 (вес сервера сервера B), 8-7 (вес сервера B) = 1,

1

Я не знаю, будет ли в блоге специальная обработка символов больше или меньше, поэтому я сделаю еще один снимок экрана:image.png

Возможно, описание текста все еще недостаточно четко, его можно понять вместе с изображением ниже взрыва:image.png

  • Если сгенерированное случайное число равно 5, то попадаем во вторую область
  • Если сгенерированное случайное число равно 10, то оно попадает в третий блок

код показывает, как показано ниже:

public class WeightRandom {
    static Servers servers = new Servers();
    static Random random = new Random();

    public static String go() {
        int allWeight = servers.map.values().stream().mapToInt(a -> a).sum();
        var number = random.nextInt(allWeight);
        for (var item : servers.map.entrySet()) {
            if (item.getValue() >= number) {
                return item.getKey();
            }
            number -= item.getValue();
        }
        return "";
    }

    public static void main(String[] args) {
        for (var i = 0; i < 15; i++) {
            System.out.println(go());
        }
    }
}

результат операции:image.png

Хотя этот метод реализации является относительно «кольцевым» по сравнению с первым методом реализации, он является лучшим методом реализации. Нет потери памяти, и размер веса не имеет ничего общего с размером списка серверов.

голосование

Существует три типа опроса: 1. Полное опрос 2. Взвешенное опрос 3. Гладкий взвешенный опрос

Завершить опрос

public class FullRound {

    static Servers servers = new Servers();
    static int index;

    public static String go() {
        if (index == servers.list.size()) {
            index = 0;
        }
        return servers.list.get(index++);
    }


    public static void main(String[] args) {
        for (var i = 0; i < 15; i++) {
            System.out.println(go());
        }
    }
}

результат операции:image.png

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

взвешенная круговая система

Есть еще две распространенные реализации взвешенного опроса, которые такие же, как взвешенный случайный.Здесь я продемонстрирую тот, который я считаю лучше:

public class WeightRound {

    static Servers servers = new Servers();

    static int index;

    public static String go() {
        int allWeight = servers.map.values().stream().mapToInt(a -> a).sum();
        int number = (index++) % allWeight;
        for (var item : servers.map.entrySet()) {
            if (item.getValue() > number) {
                return item.getKey();
            }
            number -= item.getValue();
        }
        return "";
    }

    public static void main(String[] args) {
        for (var i = 0; i < 15; i++) {
            System.out.println(go());
        }
    }
}

результат операции:image.png

Взвешенный круговой алгоритм, который выглядит хорошо, но есть небольшая недоработка, когда нагрузка на одном из серверов может резко возрасти, в то время как другой сервер «расслаблен, пьет кофе, смотрит новости». Мы надеемся, что хотя он и основан на опросе, лучше всего иметь пересечение посередине, поэтому появляется третий алгоритм опроса: сглаженный взвешенный опрос.

гладкий взвешенный круговой алгоритм

Сглаживание - это взвешенный алгоритм, это удивительные алгоритмы, нам нужно сначала объяснить этот алгоритм. Тяжелый вес, такой как сервер 5, вес B - это вес сервера, веса сервера RE C равно 1. Вес, которые мы называем «фиксированным весом», поскольку это называется «фиксированный вес», поэтому уверены, что называли «не фиксированные веса», да, «нефиксированный вес» каждый раз меняется в зависимости от определенных правил.

  1. Для первого посещения «нефиксированные веса» ABC равны 5 1 1 (начальные), потому что 5 — самый большой, а 5 соответствует серверу A, поэтому на этот раз выбран сервер A, а затем мы используем текущий Вес выбранного сервера - сумма весов каждого сервера, то есть вес сервера А - сумма весов каждого сервера. То есть 5-7=-2, "нефиксированный вес" невыбранного сервера не меняется, и теперь "нефиксированный вес" трех серверов равен -2 1 1 соответственно.
  2. Для второго визита сложите «нефиксированный вес» + «фиксированный вес», полученный при первом посещении.Теперь «нефиксированный вес» трех серверов равен 3, 2 и 2, потому что 3 — самый большой среди их, а 3 соответствует серверу A, поэтому выбранный на этот раз сервер — это A, а затем мы используем вес выбранного в данный момент сервера — сумму весов каждого сервера, то есть вес сервера A - сумма весов каждого сервера. То есть 3-7=-4, "нефиксированный вес" невыбранного сервера не меняется, и теперь "нефиксированный вес" трех серверов равен -4 2 2 соответственно.
  3. Для третьего визита добавьте «нефиксированный вес» + «фиксированный вес», полученный во время второго визита.Теперь «нефиксированный вес» трех серверов равен 1, 3 и 3. В настоящее время, хотя 3 является самым большим, но их два, мы выбираем первый, первые 3 соответствуют серверу B, поэтому сервер, выбранный на этот раз, является B, а затем мы используем вес выбранного в данный момент сервера - сумму веса каждого сервера, То есть вес сервера Б - сумма весов каждого сервера. То есть 3-7=-4, "нефиксированный вес" невыбранного сервера не меняется, и теперь "нефиксированный вес" трех серверов равен 1 -4 3 соответственно.

... С помощью этого типа, наконец, получите эту форму:

просить Неповторимый прямо перед сервером, чтобы получить тяжелые выбранный сервер Нефиксированный вес после получения сервера
1 {5, 1, 1} A {-2, 1, 1}
2 {3, 2, 2} A {-4, 2, 2}
3 {1, 3, 3} B {1, -4, 3}
4 {6, -3, 4} A {-1, -3, 4}
5 {4, -2, 5} C {4, -2, -2}
6 {9, -1, -1} A {2, -1, -1}
7 {7, 0, 0} A {0, 0, 0}
8 {5, 1, 1} A {-2, 1, 1}

Когда на 8-й раз "нефиксированный вес" возвращается к исходному 5 1 1, разве это не удивительно, может алгоритм все еще относительно круглый, но код гораздо проще:

public class Server {

    public Server(int weight, int currentWeight, String ip) {
        this.weight = weight;
        this.currentWeight = currentWeight;
        this.ip = ip;
    }

    private int weight;

    private int currentWeight;

    private String ip;

    public int getWeight() {
        return weight;
    }

    public void setWeight(int weight) {
        this.weight = weight;
    }

    public int getCurrentWeight() {
        return currentWeight;
    }

    public void setCurrentWeight(int currentWeight) {
        this.currentWeight = currentWeight;
    }

    public String getIp() {
        return ip;
    }

    public void setIp(String ip) {
        this.ip = ip;
    }
}
public class Servers {
    public HashMap<String, Server> serverMap = new HashMap<>() {
        {
            put("192.168.1.1", new Server(5,5,"192.168.1.1"));
            put("192.168.1.2", new Server(1,1,"192.168.1.2"));
            put("192.168.1.3", new Server(1,1,"192.168.1.3"));
        }
    };
}
public class SmoothWeightRound {

    private static Servers servers = new Servers();

    public static String go() {
        Server maxWeightServer = null;

        int allWeight = servers.serverMap.values().stream().mapToInt(Server::getWeight).sum();

        for (Map.Entry<String, Server> item : servers.serverMap.entrySet()) {
            var currentServer = item.getValue();
            if (maxWeightServer == null || currentServer.getCurrentWeight() > maxWeightServer.getCurrentWeight()) {
                maxWeightServer = currentServer;
            }
        }
        assert maxWeightServer != null;
        maxWeightServer.setCurrentWeight(maxWeightServer.getCurrentWeight() - allWeight);

        for (Map.Entry<String, Server> item : servers.serverMap.entrySet()) {
            var currentServer = item.getValue();
            currentServer.setCurrentWeight(currentServer.getCurrentWeight() + currentServer.getWeight());
        }
        return maxWeightServer.getIp();
    }

    public static void main(String[] args) {
        for (var i = 0; i < 15; i++) {
            System.out.println(go());
        }
    }
}

результат операции:image.png

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

хэш

The hash algorithm in the load balancing algorithm is to generate a hash value based on a value, and then correspond to a server, of course, according to the user, or if you want to come according to the request, or if you want to приходить.如果根据用户,就比较巧妙的解决了负载均衡下Session共享的问题,用户小明走的永远是A服务器,用户小笨永远走的是B服务器。

Итак, как это реализовать в коде, здесь нам нужно ввести новое понятие:хэш кольцо.

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

Хэш-ринг — это кольцо! Это... кажется... немного сложно объяснить, давайте нарисуем картинку, чтобы проиллюстрировать это.

image.png

Окружность состоит из бесконечного числа точек. Это простейшее математическое знание. Я считаю, что каждый может понять его. То же самое верно и для хеш-кольца. Хэш-кольцо также состоит из бесчисленных «хеш-точек». Термин " Hash Point» просто для удобства всеобщего понимания.

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

Когда приходит запрос, мы хэшируем его по определенному значению, если вычисленное значение хэша попадает в середину A и B, то по часовой стрелке запрос отправляется на сервер B.

Идеал полный, реальность очень худая, размер "области" под управлением трех серверов может сильно отличаться, либо просто один из серверов неисправен, и возникнут следующие ситуации:

image.png

Как видно, заряд «района» слишком велик, B можно сказать, что «очень расслаблен, пить кофе, наблюдая за фильмом», как в этом случае, называется «перекос хэша".

Так как же избежать этой ситуации?виртуальный узел.

Что такое виртуальная нода, грубо говоря, виртуальная нода... Вроде... без пояснений... Пока последняя некрасивая картинка:image.pngСреди них квадрат - реальный узел, или реальный сервер, а пятиугольник - виртуальный узел, или виртуальный сервер.Когда запрос приходит и попадает между А1 и В1, то по правилу часовой стрелки, Он должен быть обработан сервером B1, но сервер B1 является виртуальным, он сопоставляется с сервером B, поэтому он передается серверу B для обработки.

Чтобы реализовать этот алгоритм балансировки нагрузки, вам нужно использовать карту, которая обычно не используется: TreeMap, Друзья, которые не знают TreeMap, могут сначала узнать о TreeMap, и код выпущен ниже:

    private static String go(String client) {
        int nodeCount = 20;
        TreeMap<Integer, String> treeMap = new TreeMap();
        for (String s : new Servers().list) {
            for (int i = 0; i < nodeCount; i++)
                treeMap.put((s + "--服务器---" + i).hashCode(), s);
        }
        int clientHash = client.hashCode();
        SortedMap<Integer, String> subMap = treeMap.tailMap(clientHash);
        Integer firstHash;
        if (subMap.size() > 0) {
            firstHash = subMap.firstKey();
        } else {
            firstHash = treeMap.firstKey();
        }
        String s = treeMap.get(firstHash);
        return s;
    }

    public static void main(String[] args) {
        System.out.println(go("今天天气不错啊"));
        System.out.println(go("192.168.5.258"));
        System.out.println(go("0"));
        System.out.println(go("-110000"));
        System.out.println(go("风雨交加"));
    }

результат операции:image.png

На этом алгоритм балансировки хеш-нагрузки заканчивается.

минимальное давление

Алгоритм балансировки минимальной нагрузки нагрузки состоит в том, чтобы выбрать сервер, который в данный момент является наиболее «досуговым».Если у сервера А 100 запросов, у сервера Б 5 запросов, а у сервера С всего 3 запроса, то сервер С, несомненно, будет выбран. Алгоритм балансировки нагрузки более научный. К сожалению, в текущем сценарии невозможно смоделировать «аутентичный» алгоритм балансировки нагрузки с минимальным давлением.

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

На этом сегодня заканчивается, всем спасибо.