Наиболее подробное объяснение пяти структур данных Redis (теория + реальный бой) рекомендуется собрать.

Redis

Карта мозга из этой статьи

предисловие

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

Я также написал две статьи о Redis ранее, и объем чтения и отзывы читателей в порядке.Первая статья посвящена трем основным проблемам кэширования Redis[].

Вторая статья — стратегия Redis по управлению и устранению памяти [].

Это третья статья о Redis, в которой в основном объясняются пять структур данных Redis, включая реализацию основных принципов этих пяти структур данных.

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

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

  1. String: Тип строки
  2. List: Тип списка
  3. Set: неупорядоченный тип коллекции
  4. ZSet: Тип отсортированной коллекции
  5. Hash: Тип хеш-таблицы

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

Основные объекты Redis

В Redis есть"основной объект"называетсяredisObject, используется для представления всех ключей и значений, представленных структурой redisObject.String、Hash、List、Set、ZSetПять типов данных.

redisObjectИсходный код находится вredis.h, написанный на языке C, можете проверить сами, если интересно Я нарисовал здесь картинку про redisObject, указав, что структура redisObject следующая:

闪瞎人的五颜六色图
Красочная иллюстрация слепого человека

в redisObject"тип указывает, к какому типу данных он принадлежит, а кодировка указывает, как данные хранятся", то есть структура данных типа данных, реализованного нижележащим слоем. Поэтому в этой статье специально представлена ​​часть, соответствующая кодированию.

Тогда кодировка типа хранилища и что она означает соответственно? Значение определенного типа данных, представленных, как показано ниже:

图片截图出自《Redis设计与实现第二版》
Скриншот изображения от "Redis Design и внедрение второго издания"

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

В качестве простого примера вы устанавливаете строку в Rediskey 234, а затем посмотрите на тип хранения этой строки, и вы увидите, что это тип int, а нецелочисленный тип использует тип хранения embstr. Конкретная операция показана на следующем рисунке:

Тип строки

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

Существует три способа хранить структуру данных типа строкиint、raw、embstr. Так в чем же разница между этими тремя методами хранения?

int

В Redis оговорено, что если хранилище"Целое значение",Напримерset num 123Такой тип будет храниться в методе хранения int, в redisObject"атрибут ptr"Значение будет сохранено в .

SDS

если хранится"Строка является строковым значением и длиннее 32 байт."буду использоватьSDS(simple dynamic string)способ хранения, а кодировка установлена ​​как необработанная; если"Длина строки меньше или равна 32 байтам"Кодировка будет изменена на embstr, чтобы сохранить строку.

СДС называется"простая динамическая строка"Для трех свойств, определенных в SDS в исходном коде Redis.int len、int free、char buf[].

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

Поэтому при сохранении строки Hello в Redsi описание исходного кода Redis можно оформить в виде структуры Redisobject в виде SDS, как показано на следующем рисунке:

Сравнение строк SDS и языка c

У Redis определенно есть свои преимущества в использовании SDS в качестве типа для хранения строк.По сравнению со строками на языке C, SDS разработал и оптимизировал строки на языке C. Конкретные преимущества заключаются в следующем:

(1) Строки в языке c не записывают свою собственную длину, поэтому"Каждый раз, когда получается длина строки, она будет проходиться, а временная сложность равна O (n)", а в Redis вам нужно только прочитать значение len, чтобы получить строку, и временная сложность становится равной O(1).

(2)"c язык"Если две строки объединены, а пространство памяти достаточной длины не выделено,"происходит переполнение буфера""SDS"Сначала он будет судить, соответствует ли пространство требованиям в соответствии с атрибутом len.Если места недостаточно, соответствующее пространство будет расширено, поэтому"нет переполнения буфера".

(3) SDS также предоставляет"предварительное выделение пространства"и"Выпуск инертного пространства"Две стратегии. Когда строка выделяет пространство, выделение пространства должно быть больше, чем фактическое, так что мы можем"Уменьшите количество перераспределений памяти, вызванных непрерывным ростом строки выполнения.".

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

Конкретные принципы предварительного распределения пространства:"Когда длина измененной строки len меньше 1 МБ, пространство той же длины, что и len, будет предварительно выделено, то есть len=free; если len больше 1 МБ, пространство, выделенное free, будет равно 1 МБ.".

(4) SDS Binary Safe, чем можно хранить в дополнение к строке, может также хранить двоичные файлы (файлы данных, такие как двоичные изображения, аудио, видео и т. Д.); и строка языка C представляет собой пустую строку в качестве терминатора, некоторые Картинки содержат окончания, а не бинарный сейф.

Для удобства понимания составлена ​​таблица сравнения строк языка C и SDS, как показано ниже:

строка языка c SDS
Временная сложность получения длины O (n) Временная сложность получения длины O (1)
не бинарный безопасный безопасен для двоичных файлов
Хранить только строку Может также сохранять двоичные данные
Увеличение строки в n раз неизбежно приведет к n выделению памяти. Количество раз выделения памяти n раз для роста строки

Приложение строкового типа

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

(1) Во-первых, закодируйте загруженное изображение.Здесь класс инструмента написан для обработки изображения в форме кодировки Base64.Конкретный код реализации выглядит следующим образом:

 /**
     * 将图片内容处理成Base64编码格式
     * @param file
     * @return
     */
    public static String encodeImg(MultipartFile file) {
        byte[] imgBytes = null;
        try {
            imgBytes = file.getBytes();
        } catch (IOException e) {
            e.printStackTrace();
        }
        BASE64Encoder encoder = new BASE64Encoder();
        return imgBytes==null?null:encoder.encode(imgBytes );
    }

(2) Второй шаг состоит в том, чтобы сохранить формат строки обрабатываемого изображения в Redis. Реализованный код выглядит следующим образом:

    /**
     * Redis存储图片
     * @param file
     * @return
     */
    public void uploadImageServiceImpl(MultipartFile image) {
        String imgId = UUID.randomUUID().toString();
        String imgStr= ImageUtils.encodeImg(image);
        redisUtils.set(imgId , imgStr);
        // 后续操作可以把imgId存进数据库对应的字段,如果需要从redis中取出,只要获取到这个字段后从redis中取出即可。
    }

Таким образом реализовано бинарное хранение изображения.Разумеется, структура данных типа String также имеет регулярный счет:"Статистика по количеству микроблогов, статистика по количеству поклонников"Ждать.

Тип хеша

Есть два типа хеш-объектов.ziplist、hashtable, где клавиша хранения Hashtable имеет строку типа, а значение также в форме строки.key valueхранится в форме.

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

Словарь

Оба они будут вычислять индекс массива через ключ при их добавлении.Разница в том, что метод расчета разный.В HashMap используется хэш-функция.После вычисления хеш-значения в хеш-таблице атрибут sizemask и атрибут sizemask хеш-значение также используется.Повторно получите индекс массива.

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

rehash

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

Здесь, как и в HashMap, будет выполняться операция повторного хеширования для повторного хеширования аранжировки. Из рисунка выше видно, что естьht[0]иht[1]Два объекта, давайте сначала посмотрим, для чего используются свойства объекта.

В определении структуры хеш-таблицы есть четыре атрибута:dictEntry **table、unsigned long size、unsigned long sizemask、unsigned long usedсоответственно означает, что"Массив хэш-таблицы, размер хеш-таблицы, используемый для вычисления значения индекса, всегда равен size-1, количеству существующих узлов в хеш-таблице.".

ht[0] используется для первоначального хранения данных.При расширении или сжатии размер ht[0] определяет размер ht[1], и все пары ключ-значение в ht[0] будут перехешированы в ht[1 ].

Операция расширения: размер расширения ht[1] равен первой целой степени числа 2, более чем в два раза превышающей текущее значение ht[0].used Операция сжатия: первое значение ht[0].used больше или равно до 2 целых степеней .

Когда все ключи [0] перехэшировать пару ht ht[.1], пересчитываются значения для всех индексов массива, после переноса данных, когда [0] будет освобожден ht, то ht[.1] вместо ht[0] , и вновь созданный ht [1], для очередного расширения и сужения препарата.

прогрессивная перефразировка

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

Redis делит все операции повторного хэширования на несколько шагов, пока все повторное хеширование не будет завершено Конкретная реализация такая же, как и в объекте.rehashindexсвязанные с имуществом,"Если rehashindex представлен как -1, это означает, что нет операции перехеширования.".

Когда начнется операция повторного хеширования, значение будет изменено на 0 в процессе прогрессивного повторного хеширования."Обновление, удаление, запрос будут выполняться как в ht[0], так и в ht[1]", например, чтобы обновить значение, сначала обновите ht[0], а затем обновите ht[1].

Новая операция добавляется непосредственно в таблицу ht[1], а ht[0] не добавляет никаких данных, что гарантирует, что"ht[0] только уменьшается, но не увеличивается, пока в последний момент не станет пустой таблицей", поэтому операция перефразирования завершена.

Вышеизложенный принцип реализации нижней HashTable словаря.После принципа реализации HashTable, давайте посмотрим на два метода хранения структуры данных Hash."ziplist (список zip)"

ziplist

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

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

Значение каждого узла в сжатом списке следующее:

  1. zlbytes: Размер 4 байта, запись количества байтов памяти, занимаемых сжатым списком.
  2. zltail: размером 4 байта записывают смещение конечного узла таблицы от начального адреса, которое используется для быстрого определения адреса конечного узла.
  3. zllen: 2 bytes по размеру, записывая количество узлов в сжатом списке.
  4. entry: Чтобы каждый узел в списке.
  5. zlend: специальный символ конца, указывающий на сжатый список'0xFF'.

Каждый узел записи в списке рекомпрессии состоит из трех частей, включаяprevious_entry_ength、encoding、content.

  1. previous_entry_engthУказывает длину записи предыдущего узла, которую можно использовать для вычисления фактического адреса предыдущего узла, поскольку их адреса следуют друг за другом.
  2. кодировка: здесь сохраняются тип и длина контента.
  3. content: content хранит содержимое каждого узла.

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

Сценарии применения

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

Хэш также можно использовать для создания уникальных идентификаторов с помощью Redis в сценариях с высокой степенью параллелизма. Ниже мы используем эти два сценария в качестве реализации кодирования случаев.

Хранить пользовательские данные

В первом сценарии, например, мы хотим хранить информацию о пользователе.Как правило, идентификатор пользователя используется в качестве значения ключа для сохранения уникальности, а другая информация о пользователе (адрес, возраст, день рождения, номер телефона и т. д.) хранится как значение value.

Если традиционная реализация заключается в инкапсулировании информации о пользователе в объект и сохранении данных посредством сериализации, когда необходимо получить информацию о пользователе, информация о пользователе будет получена посредством десериализации.

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

Если хэш Redis используется для хранения пользовательских данных, исходное значение value будет рассматриваться как контейнер хранения в форме k v, что не приведет к проблеме накладных расходов на сериализацию.

Распределенная генерация уникальных идентификаторов

Второй сценарий заключается в создании распределенного уникального идентификатора. В этом сценарии Redis инкапсулируется в класс инструмента для реализации. Код реализации выглядит следующим образом:

    // offset表示的是id的递增梯度值
    public Long getId(String key,String hashKey,Long offset) throws BusinessException{
        try {
            if (null == offset) {
                offset=1L;
            }
            // 生成唯一id
            return redisUtil.increment(key, hashKey, offset);
        } catch (Exception e) {
            //若是出现异常就是用uuid来生成唯一的id值
            int randNo=UUID.randomUUID().toString().hashCode();
            if (randNo < 0) {
                randNo=-randNo;
            }
            return Long.valueOf(String.format("%16d", randNo));
        }
    }

Тип списка

Списки в Redis до версии 3.2 создавались с использованиемziplistиlinkedlistбыть реализованным. Введено в версиях после 3.2quicklist.

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

Linkedlist — это двусвязный список, который, как и обычный связанный список, состоит из указателей на предыдущие и предыдущие узлы. Временная сложность вставки, модификации и обновления составляет O (1), но временная сложность запроса действительно O (n).

Базовая реализация связанных списков и быстрых списков реализуется с помощью связанных списков. В языке C нет встроенной структуры данных связанных списков. Redis реализует собственную структуру связанных списков.

Особенности связанных списков в Redis:

  1. Каждый узел имеет указатели на предыдущий узел и следующий узел.
  2. Указатели prev и next головного узла и хвостового узла указывают на null, поэтому связанный список является ациклическим.
  3. Связанный список имеет свою собственную информацию о длине, а временная сложность получения длины составляет O(1).

Реализация List в Redis относительно проста, давайте рассмотрим сценарии его применения.

Сценарии применения

Списки в Redis можно реализовать"очередь блокировки", в сочетании с командами lpush и brpop. Производитель использует lupsh для вставки элементов из левой части списка, а потребитель использует команду brpop для получения элементов из правой части очереди для потребления.

(1) Сначала настроим конфиг редиса, для удобства ставлю прямоapplication.ymlВ файле конфигурации вы можете поместить файл конфигурации Redis вredis.propertiesФайлы размещаются отдельно, а конкретная конфигурация выглядит следующим образом:

spring
	redis:
		host: 127.0.0.1
		port: 6379
		password: user
		timeout: 0
		database: 2
		pool:
			max-active: 100
			max-idle: 10
			min-idle: 0
			max-wait: 100000

(2) Второй шаг — создать класс конфигурации для Redis, называемыйRedisConfig, и помечены@ConfigurationПримечания, что он является классом конфигурации.

@Configuration
public class RedisConfiguration {

@Value("{spring.redis.host}") private String host; @Value("{spring.redis.port}") private int port; @Value("{spring.redis.password}") private String password; @Value("{spring.redis.pool.max-active}") private int maxActive; @Value("{spring.redis.pool.max-idle}") private int maxIdle; @Value("{spring.redis.pool.min-idle}") private int minIdle; @Value("{spring.redis.pool.max-wait}") private int maxWait; @Value("{spring.redis.database}") private int database; @Value("${spring.redis.timeout}") private int timeout;

@Bean public JedisPoolConfig getRedisConfiguration(){ JedisPoolConfig jedisPoolConfig= new JedisPoolConfig(); jedisPoolConfig.setMaxTotal(maxActive); jedisPoolConfig.setMaxIdle(maxIdle); jedisPoolConfig.setMinIdle(minIdle); jedisPoolConfig.setMaxWaitMillis(maxWait); return jedisPoolConfig; }

@Bean public JedisConnectionFactory getConnectionFactory() { JedisConnectionFactory factory = new JedisConnectionFactory(); factory.setHostName(host); factory.setPort(port); factory.setPassword(password); factory.setDatabase(database); JedisPoolConfig jedisPoolConfig= getRedisConfiguration(); factory.setPoolConfig(jedisPoolConfig); return factory; }

@Bean public RedisTemplate<?, ?> getRedisTemplate() { JedisConnectionFactory factory = getConnectionFactory(); RedisTemplate<?, ?> redisTemplate = new StringRedisTemplate(factory); return redisTemplate; } }

(3) Третий шаг — создать Redis Util, класс инструментов Redis.Поскольку я изучил объектно-ориентированный подход, мне нравится разбирать некоторые общие вещи на классы инструментов, например части, одну за другой, и собирать их по мере необходимости.

@Component
public class RedisUtil {

@Autowired private RedisTemplate<String, Object> redisTemplate; /**

  • 存消息到消息队列中
  • @param key 键
  • @param value 值
  • @return */ public boolean lPushMessage(String key, Object value) { try { redisTemplate.opsForList().leftPush(key, value); return true; } catch (Exception e) { e.printStackTrace(); return false; } }

/**

  • 从消息队列中弹出消息 - <rpop:非阻塞式>
  • @param key 键
  • @return */ public Object rPopMessage(String key) { try { return redisTemplate.opsForList().rightPop(key); } catch (Exception e) { e.printStackTrace(); return null; } }

/**

  • 查看消息
  • @param key 键
  • @param start 开始
  • @param end 结束 0 到 -1代表所有值
  • @return */ public List<Object> getMessage(String key, long start, long end) { try { return redisTemplate.opsForList().range(key, start, end); } catch (Exception e) { e.printStackTrace(); return null; } }

Это завершает создание класса инструмента очереди сообщений Redis, который можно использовать непосредственно в коде позади.

Установить коллекцию

И списки, и наборы в Redis могут использоваться для хранения строк, но"Набор является неповторяемой коллекцией, тогда как список может хранить одну и ту же строку.", коллекция Set неупорядочена по сравнению с упорядоченной коллекцией ZSet, описанной ниже.

Базовая реализация Set"ht и инсет", ht (хеш-таблица) уже подробно разбирались ранее, давайте взглянем на структуру хранения вставного типа.

inset, также известный как целочисленный набор, представляет собой тип структуры данных, используемый для хранения целочисленных значений.int16_t,int32_tилиint64_tцелочисленное значение .

В наборе целых чисел есть три значения атрибутаencoding、length、contents[], которые представляют метод кодирования, длину набора целых чисел и содержимое элемента соответственно. Длина — это размер записанного содержимого.

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

  1. Сначала расширяется размер базового массива, а тип массива соответствует типу нового элемента.
  2. Затем преобразуйте элементы исходного массива в тип новых элементов и поместите их в соответствующие позиции расширенного массива.
  3. После того, как целочисленное собрание модернизируется, он не упадет снова, и кодировка останется модернизированной.

Сценарии применения

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


@RequestMapping(value = "/addFriend", method = RequestMethod.POST)
public Long addFriend(User user, String friend) {
    String currentKey = null;
    // 判断是否是当前用户的好友
    if (AppContext.getCurrentUser().getId().equals(user.getId)) {
        currentKey = user.getId.toString();
    }
    //若是返回0则表示不是该用户好友
    return currentKey==null?0l:setOperations.add(currentKey, friend);
}

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

public Set intersectFriend(User userA, User userB) {
    return setOperations.intersect(userA.getId.toString(), userB.getId.toString());
}

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

Коллекция ZSet

ZSet — это упорядоченный набор.Из приведенного выше рисунка видно, что базовая реализация ZSet — этоziplistиskiplistРеализовано, ziplist подробно описан выше, здесь поясняется структура skiplist.

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

Skiplist имеет следующие характеристики:

  1. Слоев много, количество узлов постепенно увеличивается сверху вниз, а узлы в верхнем слое самые разреженные и имеют наибольший размах.
  2. Каждый слой представляет собой упорядоченный связанный список, который содержит только два узла: головной узел и хвостовой узел.
  3. Каждый узел каждого уровня содержит указатель на следующий узел того же уровня и такой же узел местоположения следующего слоя.
  4. Если узел появляется на определенном уровне, то все связанные списки ниже него появятся в той же позиции.

Конкретная схема структуры реализации выглядит следующим образом:

В структуре таблицы переходов голова и хвост представляют собой указатели на головной узел и хвостовой узел, которые могут быстро реализовать позиционирование. level представляет количество слоев, len представляет длину таблицы переходов, а BW представляет задний указатель, который используется при переходе от хвоста к началу.

Ниже BW также есть два значения, которые представляют счет и объект-член (объект-член, сохраняемый каждым узлом).

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

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

Сценарии применения

Поскольку ZSet представляет собой упорядоченный набор, ZSet относительно часто реализует бизнес-типы сортировки, такие как рекомендации 10 самых популярных сообщений на главной странице, то есть объем чтения от большего к меньшему и реализация рейтинговых списков.

Ниже приведен пример реализации первых 10 игроков в рейтинговом списке Код реализации выглядит следующим образом:

@Autowired
private RedisTemplate redisTemplate;
	/**
	 * 获取前10排名
	 * @return
	 */
    public static List<levelVO > getZset(String key, long baseNum, LevelService levelService){
        ZSetOperations<Serializable, Object> operations = redisTemplate.opsForZSet();
        // 根据score分数值获取前10名的数据
        Set<ZSetOperations.TypedTuple<Object>> set = operations.reverseRangeWithScores(key,0,9);
        List<LevelVO> list= new ArrayList<LevelVO>();
        int i=1;
        for (ZSetOperations.TypedTuple<Object> o:set){
            int uid = (int) o.getValue();
            LevelCache levelCache = levelService.getLevelCache(uid);
            LevelVO levelVO = levelCache.getLevelVO();
            long score = (o.getScore().longValue() - baseNum + levelVO .getCtime())/CommonUtil.multiplier;
            levelVO .setScore(score);
            levelVO .setRank(i);
            list.add( levelVO );
            i++;
        }
        return list;
    }

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

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