Привет всем, я Сяо Цай, Сяо Цай, который хочет быть Цай Буцаем в интернет-индустрии. Она может быть мягкой или жесткой, как она мягкая, а белая проституция жесткая!
Черт~ Не забудьте поставить мне тройку после прочтения!
Эта статья в основном знакомит
Redis相关内容
При необходимости вы можете обратиться к
Если это поможет, не забудьте点赞 ❥
Творить нелегко, проституция бессмысленна!
1) Что такое Редис
- Redis (удаленный сервер словарей) используетсяязык СиНаписано, с открытым исходным кодом (лицензия BSD) Высокая производительностьНереляционный (NoSQL)база данных пар ключ-значение
- Redis может хранить сопоставления между ключами и пятью различными типами значений.
- Redis поддерживает множество функций, таких как сохранение данных в памяти на диск, использование репликации для масштабирования производительности чтения и использование сегментирования для масштабирования производительности записи. Таким образом, скорость чтения и записи очень высока, поэтому Redis широко используется в направлении кеша, который может обрабатывать более100 000 разОперации чтения и записи, самая быстрая из известных баз данных "ключ-значение". Кроме того, Redis также часто используется для выполненияРаспределенная блокировка, а Redis поддерживаетдела,Упорство,луа-скрипт,События, управляемые LRU,Несколько кластерных решенийЖдать.
2) Почему Redis такой быстрый?
-
完全基于内存
, подавляющее большинство запросов — это операции с чистой памятью -
数据结构简单
, операция с данными также проста, структура данных в Redis специально разработана для различных сценариев -
采用单线程
, чтобы избежать ненужного переключения контекста и состояний гонки, а также нет потребления ЦП из-за переключения, вызванного многопроцессорностью или многопоточностью, не нужно учитывать различные проблемы с блокировкой, нет операции блокировки снятия блокировки, и не может возникнуть взаимоблокировка из-за потребления производительности -
使用多路 I/O 复用模型
, неблокирующий ввод-вывод
3) Преимущества и недостатки Redis
преимущество:
- Скорость чтения и записи высокая, потому что данные хранятся в памяти, аналогично HashMap.
- Поддержка расширенных типов данных, поддержка String, List, Hash, Set, SortSet (Zset)
- Поддержка транзакций (непротиворечивость и изоляция) и эффективное использование RDB и AOF для обеспечения постоянства.
- Поддерживает репликацию master-slave, хост автоматически синхронизирует данные с ведомым устройством, а также может быть выполнено разделение чтения и записи.
недостаток:
- Проблемы согласованности двойной записи в кеше и базе данных
- Лавина кеша, проникновение, проблемы с поломкой
- Redis сложно поддерживать онлайн-расширение, и расширение станет очень сложным, когда емкость кластера достигнет верхнего предела.
- Перед тем, как хост выйдет из строя, некоторые данные могут быть не синхронизированы со слейвом вовремя, после переключения IP будет введена несогласованность данных, что снижает доступность системы.
- Проблема одновременного доступа к кешу
4) Разница между Redis и Memcache
-
тип данных
- Memcached поддерживает только строковый тип
- Redis поддерживает пять различных типов данных для более гибкого решения проблем.
-
сохранение данных
- Memcached не поддерживает персистентность
- Redis поддерживает две стратегии сохранения: моментальный снимок RDB и журнал AOF.
-
распределенный
- Memcached не поддерживает распределение и может обеспечить распределенное хранилище только с помощью последовательного хеширования на стороне клиента.Этот метод требует, чтобы клиент вычислял узел, в котором находятся данные, при сохранении и запросе.
- Redis Cluster поддерживает распределенные
-
механизм управления памятью
- В Redis не все данные всегда хранятся в памяти, а некоторые значения, которые давно не используются, можно обменять на диск
- Данные Memcached всегда будут в памяти.Он делит память на блоки определенной длины для хранения данных,чтобы полностью решить проблему фрагментации памяти,но такой способ сделает утилизацию памяти не высокой,например размер блока 128 байт , храните только 100 байт данных, тогда оставшиеся 28 байтов тратятся впустую
5) Какие типы данных есть в Redis
-
Строка (строка)
- Введение:бинарный сейф
- Значения, которые можно сохранить:Строки, целые числа или числа с плавающей запятой, а также изображения в формате jpg или сериализованные объекты.
- работать:Выполнение операций над целыми строками или частями строк, а также выполнение операций увеличения или уменьшения над целыми числами и числами с плавающей запятой.
- Сценарии применения:Сделать простой кеш ключ-значение
- фактическое использование:
> set test cbuc
ok
----------------------------
> get test
"cbuc"
----------------------------
> del test
(integer) 1
----------------------------
>get test
(nil)
-
Список
- Введение:Связный список (двухсвязный список)
- Значения, которые можно сохранить:список
- работать:Нажимайте или выталкивайте элементы с обоих концов, изменяйте один или несколько элементов и сохраняйте только элементы в пределах диапазона
- Сценарии применения:Рейтинг последних новостей; очередь сообщений
- фактическое использование:
> rpush test c1
(integer) 1
----------------------------
> rpush test c2
(integer) 2
----------------------------
> rpush test c2
(integer) 3
----------------------------
> lrange test 0 -1
1) "c1"
2) "c2"
3) "c3"
----------------------------
> lindex test 1
"c2"
----------------------------
> lpop test
"c1"
----------------------------
> lrange test 0 -1
"c2"
"c2"
-
Хэш (словарь)
- Введение:Набор пар ключ-значение, тип карты в языках программирования.
- Значения, которые можно сохранить:Он подходит для хранения объектов и может изменять только значение атрибута определенного элемента, например обновление в базе данных.
- работать:Добавить, получить, удалить одну пару ключ-значение, получить все пары ключ-значение, проверить, существует ли ключ
- Сценарии применения:Сохранение, чтение и изменение пользовательских атрибутов
- фактическое использование:
> hset test name cbuc
(integer) 1
----------------------------
> hset test age 23
(integer) 1
----------------------------
> hgetall test
1)"name"
2)"cbuc"
3)"age"
4)"23"
----------------------------
> hdel test age
(integer) 1
----------------------------
> hget test name
"cbuc"
----------------------------
> hgetall test
1)"name"
2)"cbuc"
-
Набор
- Введение:Реализация хеш-таблицы, элементы не повторяются
- Значения, которые можно сохранить:неупорядоченная коллекция
- работать:Добавляйте, получайте и удаляйте один элемент, проверяйте, существует ли уже элемент в наборе, вычисляйте пересечение, объединение и разность, чтобы случайным образом получать элементы из набора
- Сценарии применения:Общие друзья; используйте уникальность для подсчета всех IP-адресов, посещающих веб-сайт.
- фактическое использование:
> sadd test c1
(integer) 1
> sadd test c2
(integer) 1
> sadd test c1
(integer) 0
----------------------------
>smembers test
1)"c1"
2)"c2"
----------------------------
> sismember test c3
(integer) 0
> sismember test c1
(integer) 1
----------------------------
> srem test c1
(integer) 1
----------------------------
> smembers test
1)"c2"
-
ZSet (сортированный набор)
- Введение:Добавьте оценку параметра веса к элементам в наборе, и элементы будут расположены в порядке оценки
- Значения, которые можно сохранить:отсортированный набор
- работать:Добавить, получить, удалить элементы, получить элементы в соответствии с диапазоном баллов или элементом, вычислить ранг ключа
- Сценарии применения:Таблица лидеров; взвешенная очередь сообщений
- фактическое использование:
> zadd test 92 math
(integer) 1
> zadd test 88 english
(integer) 1
> zadd test 92 score
(integer) 1
----------------------------
> zrange test 0 -1 withscores
1)"english"
2)"88"
3)"math"
4)"92"
----------------------------
> zrangebyscore test 80 90 withscores
1)"english"
2)"88"
----------------------------
> zrem test english
(integer) 1
----------------------------
> zrange test 0 -1 withscores
3)"math"
4)"92"
Расширенное использование:
-
BitMap
Bitmap поддерживает хранение информации побитно, что можно использовать для реализации фильтра Блума (BloomFilter). -
HyperLogLog
Обеспечивает неточную функцию подсчета дедупликации, которая больше подходит для статистики дедупликации крупномасштабных данных, таких как статистический UV -
Geospatial
Его можно использовать для сохранения географического местоположения и расчета расстояния до местоположения или расчета местоположения в соответствии с радиусом. Его можно использовать для определения находящихся поблизости людей или для расчета оптимального пути по карте.
6) Что такое постоянство Redis
Постоянство — это запись данных из памяти на диск, чтобы предотвратить потерю данных памяти, когда служба не работает.
7) Механизм сохранения Redis
Redis предоставляет два механизма сохранения: моментальный снимок RDB (по умолчанию) и AOF (механизм).
RDB
RDB (Redis DataBase) — это метод сохраняемости по умолчанию в Redis. Сохраняйте данные памяти на диск в виде снимков по определенному периоду времени, которые будут генерировать
dump.rdb
Файл данных, период снимка можно определить параметром сохранения в файле конфигурации.
принцип:
fork
иcow
. разветвление означает, что Redis встретится через некоторое времяforkДочерний процесс, дочерний поток записывает данные во временный файл RDB на диске. Когда дочерний процесс записывает временный файл, исходный RDB заменяется. Преимущество этого в том, что его можнокорова (копирование при записи) преимущество:
-
方便持久化
, есть только один файл dump.rdb -
容灾性好
, файл можно сохранить на защищенный диск -
性能最大化
, разветвите дочерний процесс, чтобы завершить операцию записи, и позвольте основному процессу продолжить обработку команды, чтобы максимизировать ввод-вывод. Используйте отдельный подпроцесс для персистентности, основной процесс не будет выполнять никаких операций ввода-вывода, обеспечивая высокую производительность Redis.
недостаток:
-
数据安全性低
, RDB сохраняется в течение определенного периода времени. Если между сохранением произойдет сбой Redis, произойдет потеря данных, поэтому этот метод больше подходит для использования, когда требования к данным не являются строгими. -
保存时间长
, если объем данных большой, сохранение снимка займет много времени
AOF
AOF (Append-Only-File) предназначен для записи каждой команды записи, выполняемой Redis, в отдельный файл журнала.При перезапуске Redis данные в постоянном файле журнала будут восстановлены.
принцип:Добавьте команду записи в конец файла AOF (Append Only File). Для использования сохраняемости AOF необходимо настроить параметры синхронизации, чтобы обеспечить синхронизацию команд записи в файл на диске. Это связано с тем, что при записи в файл содержимое не сразу синхронизируется с диском, а скорее сохраняется в буфере, а затем операционная система сама решает, когда выполнять синхронизацию с диском.
Варианты синхронизации:
опции | Частота синхронизации |
---|---|
no | Пусть ОС решает, когда синхронизировать |
always | Каждая команда записи синхронизируется |
everysec | Синхронизировать каждую секунду |
-
no:
Это не приносит большого улучшения производительности сервера, а также увеличивает количество потерь данных при сбое системы. -
always:
Серьезно снижает производительность сервера -
everysec:
Этот вариант больше подходит, он может гарантировать, что при сбое системы потеряется всего около одной секунды данных, а Redis каждую секунду выполняет синхронизацию, что почти не влияет на производительность сервера.
随着服务器写请求的增多,AOF文件会越来越大。Redis提供了一种将AOF重写的特性
auto-aof-rewrite,能够去除AOF文件中的冗余写命令
преимущество:
-
数据安全
, сохранение AOF можно настроить с атрибутом appendfsync всегда, и оно будет записано в файл AOF один раз без операции команды записи. -
一致性
Записывайте файлы в режиме добавления, даже если промежуточный сервер не работает, вы можете решить проблему согласованности данных с помощью инструмента Redis-Check-AOF.
недостаток:
- Файлы AOF больше, чем файлы RDB, и восстановление выполняется медленнее.
- Когда набор данных большой, он менее эффективен, чем запуск RDB.
Сравните два
- Файлы AOF обновляются чаще, чем RDB, и AOF предпочтительнее для восстановления данных.
- AOF более безопасен и мощен, чем RDB
- Производительность RDB лучше, чем AOF
- Если оба настроены с приоритетной загрузкой AOF
8) Как выбрать подходящий метод персистентности
Как правило, оба будут настроены. Если используется отдельноRDBвы потеряете много данных, используйте их в одиночкуAOF, ваше восстановление данных неRDBПриходите быстро, если есть проблема с системой, мы можем использовать ее в первую очередьRDBвосстановить, а затем использоватьAOFПолные данные. Холодное и горячее резервное копирование используются вместе для обеспечения высокой надежности системы.
9) Стратегия удаления ключа истечения срока действия Redis
-
定时删除
Для каждого ключа со сроком действия необходимо создать таймер, и он будет очищен сразу же после достижения срока действия. Эта стратегия может немедленно очистить данные с истекшим сроком действия, что очень удобно для памяти, однако для обработки данных с истекшим сроком действия потребуется много ресурсов ЦП, что повлияет на время отклика и пропускную способность кэша. -
惰性删除
Только при доступе к ключу будет оцениваться, истек ли срок действия ключа, и если да, то он будет удален. Эта стратегия может максимизировать экономию ресурсов ЦП, но очень неблагоприятна для памяти. В крайних случаях большое количество ключей с истекшим сроком действия может быть недоступно снова, поэтому они не будут очищены, занимая много памяти. -
定期删除
Через регулярные промежутки времени будет сканироваться определенное количество ключей в словаре с истекшим сроком действия, и ключи с истекшим сроком действия будут очищаться. Эта стратегия представляет собой компромисс между первыми двумя. Регулируя временной интервал запланированного сканирования и ограниченное время каждого сканирования, можно оптимально сбалансировать ресурсы ЦП и памяти в различных обстоятельствах.
Redis 一般同时使用 惰性过期 和 定期过期 两种过期策略
10) Стратегия устранения памяти Redis
Выборочное удаление пространств ключей со сроком действия
-
volatile-lru:
Попытки восстановить наименее используемые ключи освобождают место для вновь добавленных данных. -
volatile-random:
Восстановление случайных ключей освобождает место для вновь добавленных данных -
volatile-ttl:
Приоритет отдается повторному использованию ключей с более коротким сроком службы, чтобы у вновь добавленных данных было место для хранения.
Выборочное удаление глобального пространства ключей
-
allkeys-lru:
Попытки восстановить наименее используемые ключи освобождают место для вновь добавленных данных. -
allkeys-random:
Восстановление случайных ключей освобождает место для вновь добавленных данных -
noeviction:
Когда достигается предел памяти и клиент пытается выполнить, возвращается ошибка
11) Транзакция Redis
Суть транзакции Redis заключается вMULTI,EXEC,WATCH,DISCARDРеализовано четыре примитива. Транзакции поддерживают выполнение нескольких команд одновременно, и все команды в транзакции будут сериализованы. Во время выполнения транзакции команды в очереди будут сериализованы и выполнены последовательно, а запросы команд, отправленные другими клиентами, не будут вставлены в последовательность команд выполнения транзакции.总结:
Транзакция Redis — это одноразовое, последовательное и монопольное выполнение серии команд в очереди.
- Redis не поддерживает откат, Redis не выполняет откат при сбое транзакции, а продолжает выполнять оставшиеся команды, поэтому внутренние компоненты Redis могут оставаться простыми и быстрыми.
- Если в команде внутри транзакции возникает ошибка, все команды не будут выполнены.
- Если во время транзакции возникает ошибка времени выполнения, будет выполнена правильная команда.
四个原语
- СМОТРЕТЬ:— это оптимистичная блокировка, которая обеспечивает поведение проверки и установки (CAS) для транзакций Redis, которые могут отслеживать один или несколько ключей. Как только один из ключей будет изменен (или удален), последующие транзакции выполняться не будут, а мониторинг будет продолжаться до тех пор, пока не будет выполнена команда EXEC.
- МУЛЬТИ:Используется для запуска транзакции, всегда возвращает OK. После выполнения MULTI клиент может продолжать посылать на сервер любое количество команд.Эти команды не будут выполняться сразу, а будут помещены в очередь.При вызове команды EXEC будут выполнены все команды в очереди .
- ИСПОЛНЕНИЕ:Выполнять все команды в блоке транзакций, возвращать возвращаемые значения всех команд в блоке транзакций в том порядке, в котором выполняются команды, и возвращать контрольный nil при прерывании операции
- ОТКАЗАТЬСЯ:Вызывая DISCARD, клиент может очистить очередь транзакций и отказаться от выполнения транзакции, и клиент выйдет из состояния транзакции.
12) Redis устанавливает время истечения срока действия и действует постоянно.
Команды EXPIRE и PERSIST
13) Кэш Лавина
缓存雪崩
Это означает, что кеш дает сбой на большой площади одновременно, поэтому последующие запросы будут падать на базу данных, в результате чего база данных выдержит большое количество запросов за короткий промежуток времени и рухнет.解决方法:
- Время истечения кэшированных данных устанавливается случайным образом, чтобы предотвратить истечение срока действия большого количества данных одновременно.
- Когда объем параллелизма не особенно велик, наиболее часто используемым решением является блокировка и очередь.
- Создайте кластер: если один Redis зависнет, есть другие, которые могут продолжать работать.
14) Разбивка кеша
缓存击穿
Это означает, что данных в кеше нет, но есть данные в базе (обычно время кеша истекает), в это время из-за большого количества одновременных пользователей данные в кеше одновременно не считываются , и данные извлекаются из базы данных одновременно, что приводит к мгновенному увеличению нагрузки на базу данных.解决方法:
- Установите неограниченный срок действия данных точки доступа
- Блокировки взаимного исключения могут использоваться для обновления, чтобы гарантировать, что одни и те же данные в одном и том же процессе не будут одновременно запрашиваться в БД, что снижает нагрузку на БД.
- Используйте метод случайной отсрочки, случайным образом засыпайте на короткое время в случае сбоя, снова запрашивайте и выполняйте обновление в случае сбоя.
15) Проникновение в кэш
缓存穿透
Это относится к данным, которые не находятся в кеше и базе данных, в результате чего все запросы падают на базу данных, в результате чего база данных получает большое количество запросов за короткий промежуток времени и рушится.解决方法:
- Для данных, которые не существуют, сохраните часть данных в кеше для маркировки и установите время истечения срока действия, чтобы предотвратить повторный доступ к БД с тем же запросом данных.
- использоватьBloomFilterФильтр, фильтр Блума характеризуется обнаружением существования, если фильтр Блума не существует, то данные не должны существовать, если фильтр Блума существует, фактические данные также могут не существовать.
16) Архитектура Redis master-slave
Одномашинный Redis может поддерживать количество запросов в секунду от десятков тысяч до десятков тысяч.Кэш обычно используется для поддержкиЧтение высокого параллелизмаиз. Если машина читает и пишет одновременно, могут легко возникнуть проблемы. Поэтому будет принята архитектура ведущий-ведомый, ведущий будет обрабатывать операцию записи, а затем данные будут синхронизироваться с подчиненным, а подчиненный будет отвечать за операцию чтения. Это позволит распределить большое количество запросов и легко добиться горизонтального расширения при расширении.
При запуске подчиненного устройства он отправляет
psync
команда master , если это первая синхронизация, мастер нода сделает это один разbgsave, и одновременно записывать последующие операции модификации в памятьbuffer, после завершения будетRDBВсе файлы синхронизируются с узлом репликации, после того как узел репликации их получит, он будетRDBОбраз загружается в память, а затем записывается на локальный диск. После завершения обработки главный узел уведомляется о необходимости синхронизации записей операций, измененных в течение периода, с узлом-репликой для воспроизведения, и процесс синхронизации завершается. Последующие инкрементные данные проходят черезAOFДостаточно синхронизации логов, аналогично базе данныхbinlog.
17) Redis реализует распределенные блокировки
Проще говоря先拿setnx来争抢锁,抢到之后,再用expire给锁加一个过期时间防止锁忘记了释放
SETNX — это сокращение от [SET if Not eXists] (если не существует, то SET). Если и только если ключ не существует, установите значение ключа в значение. Если данный ключ уже существует, SETNX ничего не делает. Возвращаемое значение: установлено успешно, верните 1 . Установка не удалась, возвращает 0 .
18) Механизм синхронизации Redis
Redis может использовать синхронизацию master-slave и синхронизацию slave-slave. Когда произойдет первая синхронизация, главный узел выполнитbgsave, и одновременно записывать последующие операции модификации в памятьbuffer, после завершения будетRDBВсе файлы синхронизируются с узлом репликации, после того как узел репликации их получит, он будетRDBОбраз загружается в память, а затем записывается на локальный диск. После завершения обработки главный узел уведомляется о необходимости синхронизации записей операций, измененных в течение периода, с узлом-репликой для воспроизведения, и процесс синхронизации завершается. Последующие инкрементные данные проходят черезAOFДостаточно синхронизации логов, аналогично базе данныхbinlog.
19) Кластерный принцип Redis
Redis Sentinel фокусируется на высокой доступности: когда главный сервер выходит из строя, он автоматически повышает подчиненный уровень до главного и продолжает предоставлять услуги.
Redis Cluster (кластер) фокусируется на масштабируемости.Когда одной памяти Redis недостаточно, Cluster используется для сегментированного хранилища.
Основная предвыборная стратегия:
- Чем ниже установлен приоритет подчиненного устройства, тем выше приоритет
- При тех же обстоятельствах, чем больше данных реплицирует ведомое устройство, тем выше его приоритет.
- При тех же условиях, чем меньше рунид, тем легче его подобрать
20) Режим Redis Sentinel
Sentinels должны использовать три экземпляра для обеспечения их надежности, Sentinel + master-slave.Нет гарантии, что данные не будут потеряны, но можно гарантировать высокую доступность кластера.
工作原理:
- Каждый узел Sentinel отправляет команду PING один раз в секунду ведущим, ведомым и другим узлам Sentinel, которые он знает.
- Если экземпляр последнего действительного ответа на команду PING занимает больше времени, чемdown-after-millisecondsуказанное значение, то этот экземпляр будет помечен как субъективный автономный
- Если главный сервер помечен как субъективно отключенный, то все узлы Sentinel, которые наблюдают за этим сервером, будут подтверждать один раз в секунду, что главный сервер действительно перешел в субъективно отключенное состояние.
- Если достаточное количество Стражей (по крайней мере, количество, указанное в файле конфигурации) согласится с суждением в течение указанного периода времени, то мастер будет помечен как объективно отключенный.
- Когда главный сервер помечен как объективно отключенный, узел Sentinel будет отправлять команды INFO всем подчиненным серверам автономного главного сервера от одного раза в 10 секунд до одного раза в секунду.
- Узел Sentinel будет согласовывать статус объективно автономного главного сервера.Если он находится в состоянии SDOWN, он проголосует за автоматический выбор нового главного узла, а оставшиеся подчиненные узлы будут указывать на новый главный узел для репликации данных.
- Если в автономном режиме недостаточно главного сервера купальников узла Sentinel, целевой статус главного сервера в автономном режиме будет удален. Или, когда основной сервер возвращает действительный ответ на команду Sentinel PING, субъективный статус основного сервера в автономном режиме будет удален.
21) Проблема разделения мозга и решение
何为脑裂:
Разделение мозга кластера Redis относится к тому факту, что главный узел redis, подчиненный узел redis и дозорный кластер находятся в разных сетевых разделах из-за сетевых проблем.В настоящее время, поскольку дозорный кластер не может воспринимать существование мастера, подчиненный узел повышается до главного узла. На данный момент есть два разных мастер-узла, как мозг, разделенный на две части. В это время, если клиент продолжает записывать данные на основе исходного главного узла, новый главный узел не сможет синхронизировать данные.После того, как проблема с сетью будет решена, дозорный кластер понизит исходный главный узел до подчиненного узел Синхронизация данных с нового мастера приведет к большой потере данных
解决:
- Механизм ограждения обычно используется
- Ограждение общего хранилища: убедитесь, что только один мастер записывает данные в общее хранилище.
- Ограждение клиентов: убедитесь, что только один мастер может отвечать на запросы клиентов.
- Ограждение подчиненного устройства: убедитесь, что только один ведущий может отдавать команды подчиненному устройству.
- Изменить параметры в файле конфигурации
min-replicas-to-write 3 # 表示连接到 master 的最少slave数量
min-replicas-max-lag 10 # 表示 slave 连接到 master 的最大延迟时间
В соответствии с приведенной выше конфигурацией требуется как минимум 3 подчиненных узла, а задержка репликации данных и синхронизации не может превышать 10 секунд, иначе ведущий отклонит запрос на запись.После настройки этих двух параметров, если произойдет разделение мозга кластера, исходный главный узел будет отклонен. Запрос на запись, полученный от клиента, будет отклонен, что может уменьшить потерю данных после синхронизации данных.
Эта статья длинная, и вы видите, что здесь все хорошо, и путь к росту бесконечен.
Если вы будете усердно работать сегодня, завтра вы сможете сказать на одну вещь меньше, чтобы попросить о помощи!很久很久之前,有个传说,据说:
Не понравилось после прочтения, все хреново