задний план
В недавнем проекте требуется выполнить запрос к большой базе данных, объем данных составляет около десятков миллионов. Но в то же время требования к скорости запросов также достаточно высоки.
Эта база данных ранее не использоваласьPrestoВ случае использования Hive, используя Hive для простого запроса, скорость может быть в несколько минут. Конечно, несколько минут — это не совсем то время, которое нужно для запуска SQL, которое включает в себя сумму времени на отправку запросов, запрос данных и возврат данных. Но даже при этом такая скорость явно не может удовлетворить потребности интерактивного запроса.
Следующим нашим решением был Presto.После использования Presto скорость запроса упала до секунд. Но для интерактивного запроса интерфейсного интерфейса запросов десять секунд по-прежнему являются неприемлемым временем.
Хотя Presto гораздо быстрее Hive (FaceBook официально заявляет в 10 раз), поддержка пейджинга не очень дружелюбна. Когда я его использую, я сам реализую пагинацию в бэкенде.
В этом случае применение кеша действительно беспомощно. Разумно, оптимизация должна начинаться снизу и работать снизу вверх. Способ и эффективность оптимизации верхнего уровня кажутся ограниченными.
Зачем использовать кеш
В клиентских запросах количество совпадающих данных в одном запросе может достигать сотен или даже тысяч, которые необходимо отображать на страницах во внешнем интерфейсе. Даже если вы каждый раз запрашиваете 10 фрагментов данных, весь запрос займет 6-8 секунд. Представьте, что вы ждете 10 секунд каждый раз, когда переворачиваете страницу.
Итак, используйте кеш Redis в это время. Уменьшите количество запросов к базе данных. Совпадающие данные хранятся в базе данных вместе. Таким образом, для первого запроса требуется немного больше времени.После завершения запроса пользователь может перейти на следующую страницу за миллисекунду.
Использование шаблона redis
Spring инкапсулирует относительно мощный шаблон, то есть redisTemplate, который удобен для работы с кешем Redis во время разработки. String, List, Set, Hash, Zset можно хранить в Redis. Следующее будет представлено отдельно для List и Hash.
List
Список в Redis представляет собой простой список строк, и следующие операции являются общими.
hasKey
Чтобы определить, существует ли ключ, просто вызовитеhasKey
Вот и все. Предположим, что этот ключtest
, конкретное использование заключается в следующем.
if (redisTemplate.hasKey("test")) {
System.out.println("存在");
} else {
System.out.println("不存在");
}
range
Эта функция используется для получения данных указанного интервала из кэша Redis. Конкретное использование заключается в следующем.
if (redisTemplate.hasKey("test")) {
// 该键的值为 [4, 3, 2, 1]
System.out.println(redisTemplate.opsForList().range("test", 0, 0)); // [4]
System.out.println(redisTemplate.opsForList().range("test", 0, 1)); // [4, 3]
System.out.println(redisTemplate.opsForList().range("test", 0, 2)); // [4, 3, 2]
System.out.println(redisTemplate.opsForList().range("test", 0, 3)); // [4, 3, 2, 1]
System.out.println(redisTemplate.opsForList().range("test", 0, 4)); // [4, 3, 2, 1]
System.out.println(redisTemplate.opsForList().range("test", 0, 5)); // [4, 3, 2, 1]
System.out.println(redisTemplate.opsForList().range("test", 0, -1)); // [4, 3, 2, 1] 如果结束位是-1, 则表示取所有的值
}
delete
Удалить ключ.
List<String> test = new ArrayList<>();
test.add("1");
test.add("2");
test.add("3");
test.add("4");
redisTemplate.opsForList().rightPushAll("test", test);
System.out.println(redisTemplate.opsForList().range("test", 0, -1)); // [1, 2, 3, 4]
redisTemplate.delete("test");
System.out.println(redisTemplate.opsForList().range("test", 0, -1)); // []
size
Получите длину коллекции для этого ключа.
List<String> test = new ArrayList<>();
test.add("1");
test.add("2");
test.add("3");
test.add("4");
redisTemplate.opsForList().rightPushAll("test", test);
System.out.println(redisTemplate.opsForList().size("test")); // 4
leftPush
Мы представляем место для хранения этого значения в виде контейнера, как показано на рисунке.
container
И данные всегда берутся слева, но данные могут храниться слева или справа. слеваleftPush
, правильноrightPush
. leftPush показан ниже.
left-push
Использование заключается в следующем.
for (int i = 0; i < 4; i++) {
Integer value = i + 1;
redisTemplate.opsForList().leftPush("test", value.toString());
System.out.println(redisTemplate.opsForList().range("test", 0, -1));
}
Результат вывода консоли выглядит следующим образом.
[1]
[2, 1]
[3, 2, 1]
[4, 3, 2, 1]
leftPushAll
В основном то же самое, что и leftPush, но за один раз помещает список в стек.
List<String> test = new ArrayList<>();
test.add("1");
test.add("2");
test.add("3");
test.add("4");
redisTemplate.opsForList().leftPushAll("test", test);
System.out.println(redisTemplate.opsForList().range("test", 0, -1)); // [4, 3, 2, 1]
Конечно, ты тоже можешь
redisTemplate.opsForList().leftPushAll("test", "1", "2", "3", "4");
System.out.println(redisTemplate.opsForList().range("test", 0, -1)); // [4, 3, 2, 1]
leftPushIfPresent
иleftPush
— это та же операция, с той лишь разницей, что значение ключа обновляется тогда и только тогда, когда ключ существует. Если ключ не существует, никакие операции с данными выполняться не будут.
redisTemplate.delete("test");
redisTemplate.opsForList().leftPushIfPresent("test", "1");
redisTemplate.opsForList().leftPushIfPresent("test", "2");
System.out.println(redisTemplate.opsForList().range("test", 0, -1)); // []
leftPop
Эта функция используется для удаления самого левого элемента в нашем абстрактном контейнере выше.
List<String> test = new ArrayList<>();
test.add("1");
test.add("2");
test.add("3");
test.add("4");
redisTemplate.opsForList().rightPushAll("test", test);
redisTemplate.opsForList().leftPop("test"); // [2, 3, 4]
redisTemplate.opsForList().leftPop("test"); // [3, 4]
redisTemplate.opsForList().leftPop("test"); // [4]
redisTemplate.opsForList().leftPop("test"); // []
redisTemplate.opsForList().leftPop("test"); // []
Стоит отметить, что когда return пуст, ключ больше не существует в Redis. Если вы снова позвоните в это времяleftPushIfPresent, дополнительные данные добавить нельзя. Есть код и правда.
List<String> test = new ArrayList<>();
test.add("1");
test.add("2");
test.add("3");
test.add("4");
redisTemplate.opsForList().rightPushAll("test", test);
redisTemplate.opsForList().leftPop("test"); // [2, 3, 4]
redisTemplate.opsForList().leftPop("test"); // [3, 4]
redisTemplate.opsForList().leftPop("test"); // [4]
redisTemplate.opsForList().leftPop("test"); // []
redisTemplate.opsForList().leftPop("test"); // []
redisTemplate.opsForList().leftPushIfPresent("test", "1"); // []
redisTemplate.opsForList().leftPushIfPresent("test", "1"); // []
rightPush
rightPush показан на рисунке ниже.
right-push
Использование заключается в следующем.
for (int i = 0; i < 4; i++) {
Integer value = i + 1;
redisTemplate.opsForList().leftPush("test", value.toString());
System.out.println(redisTemplate.opsForList().range("test", 0, -1));
}
Результат вывода консоли выглядит следующим образом.
[1]
[1, 2]
[1, 2, 3]
[1, 2, 3, 4]
rightPushAll
То же, что и rightPush, сохранить список за один раз.
List<String> test = new ArrayList<>();
test.add("1");
test.add("2");
test.add("3");
test.add("4");
redisTemplate.opsForList().rightPushAll("test", test);
System.out.println(redisTemplate.opsForList().range("test", 0, -1)); // [1, 2, 3, 4]
Конечно, вы тоже можете.
redisTemplate.opsForList().rightPushAll("test", "1", "2", "3", "4");
System.out.println(redisTemplate.opsForList().range("test", 0, -1)); // [1, 2, 3, 4]
rightPushIfPresent
иrightPush
— это та же операция, с той лишь разницей, что значение ключа обновляется тогда и только тогда, когда ключ существует. Если ключ не существует, никакие операции с данными выполняться не будут.
redisTemplate.delete("test");
redisTemplate.opsForList().rightPushIfPresent("test", "1");
redisTemplate.opsForList().rightPushIfPresent("test", "2");
System.out.println(redisTemplate.opsForList().range("test", 0, -1)); // []
rightPop
Эта функция используется для удаления самого правого элемента в нашем абстрактном контейнере выше.
List<String> test = new ArrayList<>();
test.add("1");
test.add("2");
test.add("3");
test.add("4");
redisTemplate.opsForList().rightPushAll("test", test);
redisTemplate.opsForList().rightPop("test"); // [1, 2, 3]
redisTemplate.opsForList().rightPop("test"); // [1, 2]
redisTemplate.opsForList().rightPop("test"); // [1]
redisTemplate.opsForList().rightPop("test"); // []
redisTemplate.opsForList().rightPop("test"); // []
иleftPopТо же самое, вернувшись пустым, снова звонитьrightPushIfPresent, дополнительные данные добавить нельзя.
index
Получить элемент в указанной позиции в списке.
if (redisTemplate.hasKey("test")) {
// 该键的值为 [1, 2, 3, 4]
System.out.println(redisTemplate.opsForList().index("test", -1)); // 4
System.out.println(redisTemplate.opsForList().index("test", 0)); // 1
System.out.println(redisTemplate.opsForList().index("test", 1)); // 2
System.out.println(redisTemplate.opsForList().index("test", 2)); // 3
System.out.println(redisTemplate.opsForList().index("test", 3)); // 4
System.out.println(redisTemplate.opsForList().index("test", 4)); // null
System.out.println(redisTemplate.opsForList().index("test", 5)); // null
}
Стоит отметить два момента. Один из них, если индекс-1
Если это так, он вернет последний элемент списка, а если нижний индекс массива выходит за пределы, он вернетnull
.
trim
Используется для перехвата элементов указанного диапазона, вы можете понимать это какrangeтот же эффект. После прочтения приведенного ниже кода его следует сразу понять.
List<String> test = new ArrayList<>();
test.add("1");
test.add("2");
test.add("3");
test.add("4");
redisTemplate.opsForList().rightPushAll("test", test); // [1, 2, 3, 4]
redisTemplate.opsForList().trim("test", 0, 2); // [1, 2, 3]
На самом деле эффект совсем другой.range
состоит в том, чтобы получить данные в указанном интервале, иtrim
Это оставить данные в указанном интервале и удалить все данные, которые не находятся в интервале.trim
даvoid
, не возвращает данных.
remove
Используется для удаления элемента, указанного в ключе. Принимает 3 параметра: имя ключа кеша, количество событий и значение, которое нужно удалить. Есть три значения, которые можно передать в событие count:-1
,0
,1
.
-1
Обозначает удаление данных, соответствующих удаляемому значению, начиная с правого края контейнера хранения;0
Представляет удаление всех данных, соответствующих входящему значению;1
Представляет, начиная с крайнего левого края контейнера хранилища, и удаляет данные, соответствующие удаляемому значению.
List<String> test = new ArrayList<>();
test.add("1");
test.add("2");
test.add("3");
test.add("4");
test.add("4");
test.add("3");
test.add("2");
test.add("1");
redisTemplate.opsForList().rightPushAll("test", test); // [1, 2, 3, 4, 4, 3, 2, 1]
// 当计数事件是-1、传入值是1时
redisTemplate.opsForList().remove("test", -1, "1"); // [1, 2, 3, 4, 4, 3, 2]
// 当计数事件是1,传入值是1时
redisTemplate.opsForList().remove("test", 1, "1"); // [2, 3, 4, 4, 3, 2]
// 当计数事件是0,传入值是4时
redisTemplate.opsForList().remove("test", 0, "4"); // [2, 3, 3, 2]
rightPopAndLeftPush
Эта функция используется для управления данными между двумя ключами и принимает два параметра, а именно исходный ключ и целевой ключ. Эта функция сделает исходный ключrightPop, а затем используйте возвращенное значение в качестве входного параметра целевого ключа.leftPush. Конкретный код выглядит следующим образом.
List<String> test = new ArrayList<>();
test.add("1");
test.add("2");
test.add("3");
test.add("4");
List<String> test2 = new ArrayList<>();
test2.add("1");
test2.add("2");
test2.add("3");
redisTemplate.opsForList().rightPushAll("test", test); // [1, 2, 3, 4]
redisTemplate.opsForList().rightPushAll("test2", test2); // [1, 2, 3]
redisTemplate.opsForList().rightPopAndLeftPush("test", "test2");
System.out.println(redisTemplate.opsForList().range("test", 0, -1)); // [1, 2, 3]
System.out.println(redisTemplate.opsForList().range("test2", 0, -1)); // [4, 1, 2, 3]
Hash
Тип хранения — хэш, на самом деле очень легко понять. вышеList
, ключ redis можно понимать как список, а вHash
, ключ Redis можно понимать как HashMap.
put
для записи данных.
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
redisTemplate.opsForHash().put("test", "map", list.toString()); // [1, 2, 3, 4]
redisTemplate.opsForHash().put("test", "isAdmin", true); // true
putAll
Используется для одновременного добавления нескольких ключей к хеш-ключу.
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
List<String> list2 = new ArrayList<>();
list2.add("5");
list2.add("6");
list2.add("7");
list2.add("8");
Map<String, String> valueMap = new HashMap<>();
valueMap.put("map1", list.toString());
valueMap.put("map2", list2.toString());
redisTemplate.opsForHash().putAll("test", valueMap); // {map2=[5, 6, 7, 8], map1=[1, 2, 3, 4]}
putIfAbsent
Используется для записи данных в хэш-ключ. Когда ключ уже существует в хеш-ключе, никакие данные не будут записаны, и только если ключ не существует в хеш-ключе, данные будут записаны.
В то же время, если ключ Hash даже не существует, redisTemplate создаст новый ключ Hash, а затем запишет ключ.
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
redisTemplate.opsForHash().putIfAbsent("test", "map", list.toString());
System.out.println(redisTemplate.opsForHash().entries("test")); // {map=[1, 2, 3, 4]}
get
для получения данных.
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
redisTemplate.opsForHash().put("test", "map", list.toString());
redisTemplate.opsForHash().put("test", "isAdmin", true);
System.out.println(redisTemplate.opsForHash().get("test", "map")); // [1, 2, 3, 4]
System.out.println(redisTemplate.opsForHash().get("test", "isAdmin")); // true
Boolean bool = (Boolean) redisTemplate.opsForHash().get("test", "isAdmin");
System.out.println(bool); // true
String str = redisTemplate.opsForHash().get("test", "map").toString();
List<String> array = JSONArray.parseArray(str, String.class);
System.out.println(array.size()); // 4
Стоит отметить, что использованиеget
Данные, полученные функцией, имеют тип Object.
Поэтому, если вам нужно использовать тип и логический тип в приведенном выше примере, вам нужно привести его один раз.List
можно использовать типfastjson
этот инструмент для преобразования. Примеры преобразований перечислены в коде выше.
delete
Используется для удаления ключа в хеш-ключе. Это можно понимать как удаление ключа на карте.
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
List<String> list2 = new ArrayList<>();
list2.add("5");
list2.add("6");
list2.add("7");
list2.add("8");
Map<String, String> valueMap = new HashMap<>();
valueMap.put("map1", list.toString());
valueMap.put("map2", list2.toString());
redisTemplate.opsForHash().putAll("test", valueMap); // {map2=[5, 6, 7, 8], map1=[1, 2, 3, 4]}
redisTemplate.opsForHash().delete("test", "map1"); // {map2=[5, 6, 7, 8]}
values
Используется для получения всех значений ключа типа Hash.
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
redisTemplate.opsForHash().put("test", "map", list.toString());
redisTemplate.opsForHash().put("test", "isAdmin", true);
System.out.println(redisTemplate.opsForHash().values("test")); // [[1, 2, 3, 4], true]
entries
Используется для получения всех значений ключа Hash в формате Map.
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
redisTemplate.opsForHash().put("test", "map", list.toString());
redisTemplate.opsForHash().put("test", "isAdmin", true);
Map<String, String> map = redisTemplate.opsForHash().entries("test");
System.out.println(map.get("map")); // [1, 2, 3, 4]
System.out.println(map.get("map") instanceof String); // true
System.out.println(redisTemplate.opsForHash().entries("test")); // {a=[1, 2, 3, 4], isAdmin=true}
hasKey
Используется, чтобы узнать, содержит ли хэш-ключ определенный ключ.
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
redisTemplate.opsForHash().put("test", "map", list.toString());
redisTemplate.opsForHash().put("test", "isAdmin", true);
System.out.println(redisTemplate.opsForHash().hasKey("test", "map")); // true
System.out.println(redisTemplate.opsForHash().hasKey("test", "b")); // false
System.out.println(redisTemplate.opsForHash().hasKey("test", "isAdmin")); // true
keys
Используется для получения всех ключей в хеш-ключе.
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
redisTemplate.opsForHash().put("test", "map", list.toString());
redisTemplate.opsForHash().put("test", "isAdmin", true);
System.out.println(redisTemplate.opsForHash().keys("test")); // [a, isAdmin]
size
Используется для получения количества ключей, содержащихся в ключе Hash.
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
redisTemplate.opsForHash().put("test", "map", list.toString());
redisTemplate.opsForHash().put("test", "isAdmin", true);
System.out.println(redisTemplate.opsForHash().size("test")); // 2
increment
Используется для накопления ключа в хеш-ключе в соответствии с входящим значением. Передаваемое значение может быть толькоdouble
илиlong
, не принимает числа с плавающей запятой
redisTemplate.opsForHash().increment("test", "a", 3);
redisTemplate.opsForHash().increment("test", "a", -3);
redisTemplate.opsForHash().increment("test", "a", 1);
redisTemplate.opsForHash().increment("test", "a", 0);
System.out.println(redisTemplate.opsForHash().entries("test")); // {a=1}
multiGet
Он используется для получения значений нескольких ключей в хеш-ключе в пакетном режиме.
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
List<String> list2 = new ArrayList<>();
list2.add("5");
list2.add("6");
list2.add("7");
list2.add("8");
redisTemplate.opsForHash().put("test", "map1", list.toString()); // [1, 2, 3, 4]
redisTemplate.opsForHash().put("test", "map2", list2.toString()); // [5, 6, 7, 8]
List<String> keys = new ArrayList<>();
keys.add("map1");
keys.add("map2");
System.out.println(redisTemplate.opsForHash().multiGet("test", keys)); // [[1, 2, 3, 4], [5, 6, 7, 8]]
System.out.println(redisTemplate.opsForHash().multiGet("test", keys) instanceof List); // true
scan
Получить значение ключа в хеш-ключе всех совпадающих условий. Я проверил некоторую информацию, и в большинстве из них написано, что нечеткое сопоставление невозможно, я пробовал сам, и это действительно возможно. Как следует, используйтеscan
В ключе нечеткого совпадения хеш-ключа, сSCAN
ключ.
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
List<String> list2 = new ArrayList<>();
list2.add("5");
list2.add("6");
list2.add("7");
list2.add("8");
List<String> list3 = new ArrayList<>();
list3.add("9");
list3.add("10");
list3.add("11");
list3.add("12");
Map<String, String> valueMap = new HashMap<>();
valueMap.put("map1", list.toString());
valueMap.put("SCAN_map2", list2.toString());
valueMap.put("map3", list3.toString());
redisTemplate.opsForHash().putAll("test", valueMap); // {SCAN_map2=[5, 6, 7, 8], map3=[9, 10, 11, 12], map1=[1, 2, 3, 4]}
Cursor<Map.Entry<String, String>> cursor = redisTemplate.opsForHash().scan("test", ScanOptions.scanOptions().match("*SCAN*").build());
if (cursor.hasNext()) {
while (cursor.hasNext()) {
Map.Entry<String, String> entry = cursor.next();
System.out.println(entry.getValue()); // [5, 6, 7, 8]
}
}
Представляем redisTemplate
Если вы понимаете, как им пользоваться, то можете внедрить в проект redisTemplate.
Внедрение pom-зависимостей
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.0.5.RELEASE</version>
</dependency>
Новый файл конфигурации
Затем нужно создать новыйRedisConfig
конфигурационный файл.
package com.detectivehlh;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
/**
* RedisConfig
*
* @author Lunhao Hu
* @date 2019-01-17 15:12
**/
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) {
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
//redis序列化
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
jackson2JsonRedisSerializer.setObjectMapper(om);
StringRedisTemplate template = new StringRedisTemplate(factory);
template.setValueSerializer(jackson2JsonRedisSerializer);
template.setHashKeySerializer(jackson2JsonRedisSerializer);
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.setValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
инъекция
Вставьте redisTemplate там, где его нужно использовать.
@Autowired
private RedisTemplate redisTemplate;