Статьи

Написание сервера загрузки.

В эпоху ботнетов, которые вы можете арендовать за несколько сотен долларов и проводить свою собственную атаку с распределенным отказом в обслуживании, наличие аварийных переключателей, которые выборочно отключают дорогостоящую функциональность или с благодарностью снижают производительность, является огромной победой. Ваше приложение все еще работает, пока вы решаете проблему. Конечно, такие меры безопасности также ценны в часы пик или в рабочее время. Одним из таких механизмов, применяемых к серверам загрузки, является динамическое регулирование скорости загрузки. Чтобы предотвратить распределенную атаку на отказ в обслуживании и чрезмерно высокие облачные счета, рассмотрите встроенное регулирование загрузки, которое можно включить и настроить во время выполнения. Идея состоит в том, чтобы ограничить максимальную скорость загрузки, как глобально, так и для каждого клиента (IP? Соединение? Cookie? Пользовательский агент?).

Должен признать, мне нравится дизайн OutputStream с множеством простых реализаций Input / OutputStream и Reader / Writer , каждая из которых несет только одну ответственность. Вы хотите буферизацию? GZIPing? Кодировка символов? Запись файловой системы? Просто составьте нужные классы, которые всегда работают друг с другом. Хорошо, это все еще блокирует, но это было разработано до того, как реактивные хипстеры даже родились. Несмотря ни на что, java.io также следует принципу открытого-закрытого : можно просто улучшить существующий код ввода-вывода, не касаясь встроенных классов — но подключив новые декораторы. Поэтому я создал простой декоратор для InputStream который замедляет чтение ресурсов с нашей стороны, чтобы обеспечить заданную скорость загрузки. Я использую мой любимый класс RateLimiter :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
public class ThrottlingInputStream extends InputStream {
  
    private final InputStream target;
    private final RateLimiter maxBytesPerSecond;
  
    public ThrottlingInputStream(InputStream target, RateLimiter maxBytesPerSecond) {
        this.target = target;
        this.maxBytesPerSecond = maxBytesPerSecond;
    }
  
    @Override
    public int read() throws IOException {
        maxBytesPerSecond.acquire(1);
        return target.read();
    }
  
    @Override
    public int read(byte[] b) throws IOException {
        maxBytesPerSecond.acquire(b.length);
        return target.read(b);
    }
  
    @Override
    public int read(byte[] b, int off, int len) throws IOException {
        maxBytesPerSecond.acquire(len);
        return target.read(b, off, len);
    }
  
    //less important below...
  
    @Override
    public long skip(long n) throws IOException {
        return target.skip(n);
    }
  
    @Override
    public int available() throws IOException {
        return target.available();
    }
  
    @Override
    public synchronized void mark(int readlimit) {
        target.mark(readlimit);
    }
  
    @Override
    public synchronized void reset() throws IOException {
        target.reset();
    }
  
    @Override
    public boolean markSupported() {
        return target.markSupported();
    }
  
    @Override
    public void close() throws IOException {
        target.close();
    }
}

Произвольный InputStream может быть обёрнут с помощью ThrottlingInputStream так что чтение на самом деле замедляется. Вы можете создать новый RateLimiter для каждого ThrottlingInputStream или один глобальный, общий для всех загрузок. Конечно, можно утверждать, что простая RateLimiter sleep() (которую RateLimiter ) тратит много ресурсов, но давайте оставим этот пример простым и избегаем неблокирующих операций ввода-вывода. Теперь мы можем легко подключить декоратор к:

1
2
3
4
5
6
private InputStreamResource buildResource(FilePointer filePointer) {
    final InputStream inputStream = filePointer.open();
    final RateLimiter throttler = RateLimiter.create(64 * FileUtils.ONE_KB);
    final ThrottlingInputStream throttlingInputStream = new ThrottlingInputStream(inputStream, throttler);
    return new InputStreamResource(throttlingInputStream);
}

Приведенный выше пример ограничивает скорость загрузки до 64 КиБ / с — очевидно, в реальной жизни вы бы хотели, чтобы такое число настраивалось, предпочтительно во время выполнения. Кстати, мы уже говорили о важности заголовка Content-Length . Если вы следите за ходом загрузки с помощью pv , он правильно оценит оставшееся время, что очень удобно:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
~ $ curl localhost:8080/download/4a8883b6-ead6-4b9e-8979-85f9846cab4b | pv > /dev/null
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0 71.2M    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0  16kB 0:00:01 [14,8kB/s]
  0 71.2M    0 40960    0     0  30097      0  0:41:21  0:00:01  0:41:20 30095  80kB 0:00:02 [  64kB/s]
  0 71.2M    0  104k    0     0  45110      0  0:27:35  0:00:02  0:27:33 45106 144kB 0:00:03 [  64kB/s]
  0 71.2M    0  168k    0     0  51192      0  0:24:18  0:00:03  0:24:15 51184 208kB 0:00:04 [  64kB/s]
  0 71.2M    0  232k    0     0  54475      0  0:22:51  0:00:04  0:22:47 54475 272kB 0:00:05 [63,9kB/s]
  0 71.2M    0  296k    0     0  56541      0  0:22:00  0:00:05  0:21:55 67476 336kB 0:00:06 [  64kB/s]
  0 71.2M    0  360k    0     0  57956      0  0:21:28  0:00:06  0:21:22 65536 400kB 0:00:07 [  64kB/s]
  0 71.2M    0  424k    0     0  58986      0  0:21:06  0:00:07  0:20:59 65536 464kB 0:00:08 [  64kB/s]
  0 71.2M    0  488k    0     0  59765      0  0:20:49  0:00:08  0:20:41 65536 528kB 0:00:09 [  64kB/s]
  0 71.2M    0  552k    0     0  60382      0  0:20:36  0:00:09  0:20:27 65536 592kB 0:00:10 [  64kB/s]
  0 71.2M    0  616k    0     0  60883      0  0:20:26  0:00:10  0:20:16 65536 656kB 0:00:11 [  64kB/s]
  0 71.2M    0  680k    0     0  61289      0  0:20:18  0:00:11  0:20:07 65536 720kB 0:00:12 [  64kB/s]

В качестве дополнительного бонуса pv доказал наши работы по дросселированию (последний столбец). Теперь без Content-Length pv имеет никакого представления о реальном прогрессе:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
~ $ curl localhost:8080/download/4a8883b6-ead6-4b9e-8979-85f9846cab4b | pv > /dev/null
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 16384    0 16384    0     0  21116      0 --:--:-- --:--:-- --:--:-- 21113  32kB 0:00:01 [  31kB/s]
100 81920    0 81920    0     0  46149      0 --:--:--  0:00:01 --:--:-- 46126  96kB 0:00:02 [  64kB/s]
100  144k    0  144k    0     0  53128      0 --:--:--  0:00:02 --:--:-- 53118 160kB 0:00:03 [  64kB/s]
100  208k    0  208k    0     0  56411      0 --:--:--  0:00:03 --:--:-- 56406 224kB 0:00:04 [  64kB/s]
100  272k    0  272k    0     0  58328      0 --:--:--  0:00:04 --:--:-- 58318 288kB 0:00:05 [  64kB/s]
100  336k    0  336k    0     0  59574      0 --:--:--  0:00:05 --:--:-- 65536 352kB 0:00:06 [  64kB/s]
100  400k    0  400k    0     0  60450      0 --:--:--  0:00:06 --:--:-- 65536 416kB 0:00:07 [  64kB/s]
100  464k    0  464k    0     0  61105      0 --:--:--  0:00:07 --:--:-- 65536 480kB 0:00:08 [  64kB/s]
100  528k    0  528k    0     0  61614      0 --:--:--  0:00:08 --:--:-- 65536 544kB 0:00:09 [  64kB/s]
100  592k    0  592k    0     0  62014      0 --:--:--  0:00:09 --:--:-- 65536 608kB 0:00:10 [  64kB/s]
100  656k    0  656k    0     0  62338      0 --:--:--  0:00:10 --:--:-- 65536 672kB 0:00:11 [  64kB/s]
100  720k    0  720k    0     0  62612      0 --:--:--  0:00:11 --:--:-- 65536 736kB 0:00:12 [  64kB/s]

Мы видим, что данные текут, но мы не знаем, сколько осталось. Таким образом, Content-Length — действительно важный заголовок.

Написание сервера загрузки

Ссылка: Написание сервера загрузки. Часть V: Скорость загрузки газа от нашего партнера JCG Томаша Нуркевича из блога Java и соседей .