Необычное руководство по эксплуатации Redis (на основе реализации кода SpringBoot2.3.2)

задняя часть
Необычное руководство по эксплуатации Redis (на основе реализации кода SpringBoot2.3.2)

1. Введение

Обычно программисты очень быстро изучают установку и использование Redis, потому что большинство операций Redis эквивалентны логике работы Map.Пока вы понимаете большинство API-интерфейсов в сочетании с Baidu, вы можете быстро инвестировать в работу CRUD, так что приходите сегодня. первая фаза Redis не часто используется в руководстве по эксплуатации.Конечно, менее часто используемые здесь не используются каждый день, но все же есть места, где они будут применяться в проекте.

2 Установите Редис

Установка и собственные команды

3 SpringBoot2 интегрирует Redis (если у вас есть готовое окружение, то можете его пропустить)

3.1 Связанные зависимости

<!-- 单元测试 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

<!-- redis -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<!-- 连接池 --》
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>

<!-- hutool -->
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.7.17</version>
</dependency>

3.2 Файл конфигурации

spring:
  redis:
    host: 127.0.0.1   # 服务地址
    port: 6379        # 服务端口
    timeout: 10000    # 连接超时时间(毫秒)
    lettuce:
      pool:
        max-active: 8   # 最大连接数
        max-wait: -1ms  # 阻塞最大等待时长(负值代表没有限制)
        min-idle: 0     # 最小空闲连接
        max-idle: 8     # 最大空闲连接

3.3 Код конфигурации (просто поместите его в класс запуска)

@Bean
public <T> RedisTemplate<String, T> redisTemplate(RedisConnectionFactory factory) {
    RedisTemplate<String, T> template = new RedisTemplate<>();
    template.setConnectionFactory(factory);
    template.setKeySerializer(new StringRedisSerializer());
    template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
    template.setHashKeySerializer(new StringRedisSerializer());
    template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
    return template;
}

Добавьте эту конфигурацию, чтобы решить проблему искажения

4 Нечастые операции

Помощник по сохранению потока: сначала посмотрите каталог, если можете его пропустить~

4.1 СКАН (надо знать и знать)

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

4.1.1 Реализация кода

package com.example.demo.redis;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

@Component
public class RedisScan {

    /**
     * 获取一批指定前缀的key eg: key:* 获取所有key:开头的key
     *
     * @param pattern key匹配正则
     * @param count   一次获取数目
     * @return
     */
    public Set<String> scan(String pattern, int count) {
        return redisTemplate.execute((RedisCallback<Set<String>>) connection -> {
            Set<String> keysTmp = new HashSet<>();
            try (Cursor<byte[]> cursor = connection.scan(new ScanOptions.ScanOptionsBuilder()
                    .match(pattern)
                    .count(count).build())) {
                while (cursor.hasNext()) {
                    keysTmp.add(new String(cursor.next(), StandardCharsets.UTF_8));
                }
            } catch (Exception e) {
                LOGGER.error(e.getMessage(), e);
            }
            return keysTmp;
        });
    }

    /**
     * 批量删除
     *
     * @param pattern key匹配正则
     * @param step    阶梯删除的数目
     */
    public void batchDelete(String pattern, int step) {
        while (scan(pattern, step).size() > 0) {
            Set<String> keys = scan(pattern, step);
            redisTemplate.delete(keys);
        }
    }

    /**
     * 模拟指定数量的数据
     *
     * @param count
     */
    public void mock(String keyPrefix, int count) {
        Map<String, String> map = new HashMap<>();
        for (int i = 0; i < count; i++) {
            map.put(keyPrefix + i, String.valueOf(i));
        }
        redisTemplate.opsForValue().multiSet(map);
    }

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    private static final Logger LOGGER = LoggerFactory.getLogger(RedisScan.class);

}

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

4.1.2 Тестирование

package com.example.demo.redis;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.TimeInterval;
import com.example.demo.ApplicationTests;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

public class RedisScanTest extends ApplicationTests {

    @Autowired
    RedisScan redisScan;

    private static final Logger LOGGER = LoggerFactory.getLogger(RedisScanTest.class);

    @Test
    public void testMock() {
        TimeInterval timer = DateUtil.timer();
        redisScan.mock("mock:", 10000);
        LOGGER.info("耗时:{}ms", timer.interval());
    }

    @Test
    public void batchDeleteTest() {
        TimeInterval timer = DateUtil.timer();
        redisScan.batchDelete("mock:*", 1000);
        LOGGER.info("耗时:{}ms", timer.interval());
    }
}

4.2 BitMap (нужно освоить)

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

  • 1. Хранилище основано на наименьшем единичном бите, что очень экономит место.
  • 2. Временная сложность составляет O(1) при установке и O(n) при чтении, и операция выполняется очень быстро.
  • 3. Хранение бинарных данных происходит очень быстро при выполнении связанных вычислений.
  • 4. Простота расширения

4.2.1 Требования по умолчанию

Платформа имеет пользователей 1E и должна отмечать онлайн-статус каждого пользователя.

4.2.2 Реализация кода

package com.example.demo.redis;

import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

@Component
public class RedisBitMap {

    @Resource
    private RedisTemplate redisTemplate;

    private static final String ONLINE_STATUS = "online_status";

    /**
     * 设置在线状态
     *
     * @param userId
     * @param online
     */
    public void setOnlineStatus(int userId, boolean online) {
        redisTemplate.opsForValue().setBit(ONLINE_STATUS, userId, online);
    }

    /**
     * 统计在线人数
     *
     * @return
     */
    public Long getOnlineCount() {
        return (Long) redisTemplate.execute((RedisCallback<Long>) con -> con.bitCount(ONLINE_STATUS.getBytes()));
    }
}

4.2.3 Тестирование

package com.example.demo.redis;

import com.example.demo.ApplicationTests;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

public class RedisBitMapTest extends ApplicationTests {

    @Autowired
    RedisBitMap redisBitMap;

    private static final Logger LOGGER = LoggerFactory.getLogger(RedisBitMapTest.class);


    @Test
    public void setOnlineStatusTest() {
        // 10000个人
        for (int i = 0; i < 10000; i++) {
            // 设置偶数在线 奇数不在线
            redisBitMap.setOnlineStatus(i, i % 2 == 0);
        }
    }

    @Test
    public void onlineCountTest() {
        Long i = redisBitMap.getOnlineCount();
        LOGGER.info("oline count = {}", i);

    }
}

4.3 HyperLogLog (нужно освоить)

HyperLogLog, вы можете понимать его функцию как набор, который оптимизирует пространство для хранения, подходит для бизнес-сценариев, когда большие веб-сайты ведут статистику поведения пользователей.Если у вас небольшой веб-сайт, вы можете использовать набор; статистика подсчета HyperLogLog имеет определенные ошибки.Максимальная ошибка менее 1%, поэтому HyperLogLog не подходит для 100% точной статистики, а статистика посещаемости сайта обычно может принимать такие ошибки.

4.3.1 Преимущества и недостатки

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

4.3.2 Пример кода

package com.example.demo.redis;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.TimeInterval;
import com.example.demo.ApplicationTests;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;

import javax.annotation.Resource;

/**
 * RedisHyperLoglog对比Set
 *
 * @author 热黄油啤酒
 */
public class RedisHyperLogLogTest extends ApplicationTests {

    @Resource
    private RedisTemplate<String, String> redisTemplate;

    @Test
    public void hllTest() {
        TimeInterval timer = DateUtil.timer();
        String key = "pv_hll:20211220";
        // 模拟1000次操作
        for (int i = 1; i < 1000; i++) {
            redisTemplate.opsForHyperLogLog().add(key, String.valueOf(i));
        }
        Long size = redisTemplate.opsForHyperLogLog().size(key);
        LOGGER.info("size = {}, 耗时= {}ms", size, timer.interval());
        // 操作999次返回996
    }

    @Test
    public void setTest() {
        TimeInterval timer = DateUtil.timer();
        String key = "pv_set:20211220";
        // 模拟1000次操作
        for (int i = 1; i < 1000; i++) {
            redisTemplate.opsForSet().add(key, String.valueOf(i));
        }
        Long size = redisTemplate.opsForSet().size(key);
        LOGGER.info("size = {}, 耗时= {}ms", size, timer.interval());
        // 操作999次返回999
    }

    private static final Logger LOGGER = LoggerFactory.getLogger(RedisHyperLogLogTest.class);
}

Если вы умеете использовать Bitmap и HyperLogLog в сочетании с такими операциями, как автоинкремент Redis, у вас будет техническая база для бизнеса по статистике поведения веб-сайта. Система BI (поведение) Определение идентичности поведения)

4.4 Географическая информация GEO (просто знайте это)

Все основные базы данных обеспечивают поддержку географической информации, включая хранение, анализ, создание данных и т. д. Для бизнес-продуктов, связанных с географической информацией, операция Redis GEO имеет преимущества простой работы и высокой производительности, но Redis GEO обеспечивает только точечную поддержку, отсутствие поддержки для линий и плоскостей

4.4.1 Примеры кода

package com.example.demo.redis;

import org.springframework.data.geo.*;
import org.springframework.data.redis.connection.RedisGeoCommands;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 地理信息操作
 */
@Component
public class RedisGeo {

    @Resource
    private RedisTemplate redisTemplate;

    private static final String CITY = "city";

    /**
     * 添加点位
     *
     * @param name 名称
     * @param x    经度
     * @param y    纬度
     */
    public void add(String name, double x, double y) {
        redisTemplate.opsForGeo().add(CITY, new Point(x, y), name);
    }


    /**
     * 距离(km)
     *
     * @param city1
     * @param city2
     * @return
     */
    public double distance(String city1, String city2) {
        Distance distance = redisTemplate.opsForGeo().distance(CITY, city1, city2, RedisGeoCommands.DistanceUnit.KILOMETERS);
        return distance.getValue();
    }

    /**
     * 周边城市
     *
     * @param city
     * @param distance
     * @return
     */
    public List<Map<String, Object>> circum(String city, double distance) {
        // 获取中心城市坐标
        List<Point> positions = redisTemplate.opsForGeo().position(CITY, city);
        List<Map<String, Object>> cityList = new ArrayList<>();
        if (CollectionUtils.isEmpty(positions)) {
            return cityList;
        }
        Point point = positions.stream().findFirst().get();
        Circle circle = new Circle(point, new Distance(distance, Metrics.KILOMETERS));
        RedisGeoCommands.GeoRadiusCommandArgs args = RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs().includeDistance().includeCoordinates().sortAscending().limit(5);
        GeoResults<RedisGeoCommands.GeoLocation<String>> results = redisTemplate.opsForGeo()
                .radius(CITY, circle, args);
        for (GeoResult<RedisGeoCommands.GeoLocation<String>> result : results) {
            RedisGeoCommands.GeoLocation<String> content = result.getContent();
            String name = content.getName();
            Point cityPoint = content.getPoint();
            Distance cityDistance = result.getDistance();
            // 为了展示这些api的使用,我将返回值包装成map
            Map<String, Object> cityMap = new HashMap<>();
            cityMap.put("name", name);
            cityMap.put("lng", cityPoint.getX());
            cityMap.put("lat", cityPoint.getY());
            cityMap.put("distance", cityDistance.getValue());
            cityList.add(cityMap);
        }
        return cityList;
    }


}

4.4.2 Тестирование

package com.example.demo.redis;

import com.example.demo.ApplicationTests;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.List;
import java.util.Map;

public class RedisGeoTest extends ApplicationTests {
    @Autowired
    private RedisGeo redisGeo;

    @Test
    public void addTest() {
        // 添加一些城市点位
        redisGeo.add("北京", 116.405285, 39.904989);
        redisGeo.add("武汉", 114.311582, 30.598467);
        redisGeo.add("郑州", 113.631419, 34.753439);
        redisGeo.add("广州", 113.271431, 23.135336);
        redisGeo.add("南宁", 108.373451, 22.822607);
    }

    @Test
    public void distanceTest() {
        // 北京到武汉的距离
        double distance = redisGeo.distance("北京", "武汉");
        LOGGER.info("distance = {}km", distance);
    }

    @Test
    public void circumTest() {
        // 北京周边1000km的城市
        List<Map<String, Object>> circumCity = redisGeo.circum("北京", 1000);
        LOGGER.info("circum city = {}", circumCity);
    }

    private static final Logger LOGGER = LoggerFactory.getLogger(RedisGeoTest.class);
}

Redis Geo в основном охватывает основные сценарии, связанные с точками, такие как находящиеся поблизости люди, окружающие магазины и т. д. Мы можем решить эти потребности, ознакомившись с этими API. Те, у кого нет таких потребностей, также могут быть использованы в качестве резерва знаний. иметь больше Для сложных требований к хранению географической информации вы можете обратиться к моей статье об обработке географической информации MySQL-Хранение и функции пространственных данных MySQL — самородки (juejin.cn)

4.5 Очередь сообщений (просто понять)

В новой версии Redis реализована официальная поддержка очереди сообщений (конечно, это беспомощный выбор для авторов Redis, в конце концов, слишком много людей используют Redis List для обработки сообщений), мы представляем реализацию очереди сообщений на основе списка и официальную предусмотрена очередь сообщений

4.5.1 На основе списка

производство и потребление

package com.example.demo.redis;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

/**
 * 基于Redis List的消息队列实现
 */
@Component
public class RedisQueueOfList {

    private static final Logger LOGGER = LoggerFactory.getLogger(RedisQueueOfList.class);

    @Resource
    private RedisTemplate<String, String> redisTemplate;

    private static final String TOPIC = "redis_queue";


    /**
     * 发送消息
     *
     * @param msg
     */
    public void send(String msg) {
        redisTemplate.opsForList().leftPush(TOPIC, msg);
    }

    @PostConstruct
    public void listener() {
        LOGGER.info("消费者已启动...");
        new Thread() {
            public void run() {
                while (true) {
                    String msg = redisTemplate.opsForList().rightPop(TOPIC, 1, TimeUnit.SECONDS);
                    if (msg == null) {
                        continue;
                    }
                    // 业务处理
                    LOGGER.info("queue msg = {}", msg);
                }
            }
        }.start();

    }
}

тестовый интерфейс

package com.example.demo.redis.controller;

import com.example.demo.redis.RedisQueueOfList;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("redis-queue")
public class RedisQueueController {

    @GetMapping("/send")
    public void send(String msg) {
        queueOfList.send(msg);
    }

    @Autowired
    private RedisQueueOfList queueOfList;
}

Очередь сообщений, основанная на Redis List, представляет собой просто отправку списка и всплывающее окно потребительского цикла, которое можно использовать в небольших масштабах.Рекомендуется использовать более профессиональное промежуточное программное обеспечение очереди сообщений (kafka, RocketMQ...)

4.5.2 Channel

Публикация-подписка Redis (pub/sub) — это шаблон обмена сообщениями: отправители (pub) отправляют сообщения, а подписчики (sub) получают сообщения.

производство и потребление

package com.example.demo.redis;

import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.listener.PatternTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

@Component
public class RedisChannel {

    /**
     * 发送消息
     *
     * @param msg
     */
    public void send(String msg) {
        redisTemplate.convertAndSend(CHANNEL, msg);
    }

    /**
     * 注册消息监听
     *
     * @param connectionFactory
     * @param listenerAdapter
     * @return
     */
    @Bean
    public RedisMessageListenerContainer redisMessageListenerContainer(RedisConnectionFactory connectionFactory,
                                                                       MessageListenerAdapter listenerAdapter) {
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        container.addMessageListener(listenerAdapter, new PatternTopic(CHANNEL));
        return container;
    }

    /**
     * 消息监听器适配器,绑定消息处理器,利用反射技术调用消息处理器的业务方法
     *
     * @param receiver
     * @return
     */
    @Bean
    public MessageListenerAdapter messageListenerAdapter(RedisChannelReceiver receiver) {
        return new MessageListenerAdapter(receiver, "receiver");
    }

    @Resource
    private RedisTemplate<String, String> redisTemplate;

    private static final String CHANNEL = "redis_channel";
}

обработка сообщений

package com.example.demo.redis;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

/**
 * 消息接收处理
 */
@Component
public class RedisChannelReceiver {

    public void receiver(String msg) {
        // TODO 消息处理业务
        LOGGER.info("receiver msg = {}", msg);
    }

    private static final Logger LOGGER = LoggerFactory.getLogger(RedisChannelReceiver.class);
}

тестовый интерфейс

package com.example.demo.redis.controller;

import com.example.demo.redis.RedisChannel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("redis-channel")
public class RedisChannelController {

    @GetMapping("send")
    public void send(String msg) {
        redisChannel.send(msg);
    }

    @Autowired
    private RedisChannel redisChannel;
}

5 окончание

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

6 Другие мои статьи

Комбинация Redis и локального кэша — самородки (juejin.cn)

Ali Demand Challenge - Не удалось войти 5 раз подряд в течение десяти минут, вам нужно подождать 30 минут, чтобы войти [с фотографиями] - Nuggets (juejin.cn)

Одна статья для понимания процесса проверки входа пользователя (с фотографиями) — Nuggets (juejin.cn)

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