Advanced Redis (9) — интеграция Spring Boot

Spring Boot Redis задняя часть
Advanced Redis (9) — интеграция Spring Boot

Это 22-й день моего участия в августовском испытании обновлений.Подробности о событии:Испытание августовского обновления

1. Знакомство с часто используемыми клиентами Redis

После Spring Boot 2.x салат используется по умолчанию для поддержки подключения к Redis.

Интернет-адрес Jedis API:tool.OSCHINA.net/uploads/API…

Адрес официального сайта салата:lettuce.io

концепция:

Jedis: это клиент Java-реализации старомодного Redis, который обеспечивает более полную поддержку команд Redis;

Redisson: реализует распределенную и масштабируемую структуру данных Java;

Lettuce: высокоуровневый клиент Redis для потокобезопасного синхронного, асинхронного и реактивного использования, поддерживающий кластеры, Sentinel, конвейеры и кодировщики.

преимущество:

Jedis: всесторонне предоставляет операционные функции Redis;

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

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

2. Spring Boot интегрирует Jedis

Когда мы используем Spring Boot для создания микросервисов, нам по-прежнему нужен кеш Redis для кэширования некоторых данных и хранения некоторых часто используемых данных. Использовать Redis напрямую сложнее. Здесь мы используем jedis для реализации кеша Redis для достижения цели эффективного кэширования. .

2.1 Знакомство с зависимостями Jedis

 <dependency>
     <groupId>redis.clients</groupId>
     <artifactId>jedis</artifactId>
 </dependency>

2.2 Настройка application.yml

 spring:
     redis:
         port: 6379
         password: 2436
         host: 112.124.1.187
         jedis:
             pool:
                 max-idle: 6     # 连接池最大空闲连接数,默认 8
                 max-active: 10  # 连接池最大连接数(使用负值便是没有限制),默认 8
                 min-idle: 2     # 连接池最小空闲连接数,默认 0
         timeout: 2000

Spring Boot не интегрирует Jedis, поэтому вам нужно написать свой собственный класс конфигурации и настроить JedisPool

2.3 Запись конфигурации

 @Configuration
 public class JedisConfig {
 
     private Logger logger = LoggerFactory.getLogger(JedisConfig.class);
 
     @Value("${spring.redis.port}")
     private Integer port;
     @Value("${spring.redis.host}")
     private String host;
     @Value("${spring.redis.password}")
     private String password;
     @Value("${spring.redis.jedis.pool.max-idle}")
     private Integer maxIdle;
     @Value("${spring.redis.jedis.pool.max-active}")
     private Integer maxActive;
     @Value("${spring.redis.jedis.pool.min-idle}")
     private Integer minIdle;
     @Value("${spring.redis.timeout}")
     private Integer timeout;
 
 
     @Bean
     public JedisPool jedisPool(){
         JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
         jedisPoolConfig.setMaxIdle(maxIdle);
         jedisPoolConfig.setMinIdle(minIdle);
         jedisPoolConfig.setMaxTotal(maxActive);
 
         JedisPool jedisPool = new JedisPool(jedisPoolConfig,host,port,timeout,password);
 
         logger.info("Jedis连接成功:" + host + ":" + port);
 
         return jedisPool;
     }
 
 }

2.4 Тест 1: Тип строки

Требование: пользователь вводит ключ, чтобы сначала определить, существуют ли данные в Redis. Если они существуют, запросите их в Redis и верните. Если они не существуют, выполните запрос в базе данных MySQL, назначьте результат Redis и верните

 // UserService.java
 public interface UserService {
 
     /**
      * 需求:用户输入一个key
      * 先判断Redis中是否存在该数据
      *  如果存在,在Redis中进行查询,并返回
      *  如果不存在,在MySQL数据库查询,将结果赋给Redis,并返回
      *
      */
     public String getString(String key);
 
 }
 =======================
 // UserServiceImpl.java
 @Service
 @Log    // 相当于 Logger logger = LoggerFactory.getLogger(JedisConfig.class);
 public class UserServiceImpl implements UserService {
 
     @Autowired
     JedisPool jedisPool;
 
 
     @Override
     public String getString(String key) {
 
         String value = "";
         // 1.得到Jedis对象
         Jedis jedis = jedisPool.getResource();
         // 2.判断该key在redis中是否存在
         if(jedis.exists(key)){
             // 2.1 Redis中存在,
             value = jedis.get(key);
             log.info("查询Redis中的数据!");
         } else{
             // 2.2 Redis中不存在,从数据库中查询,并存入Redis中
             value = "MySQL中的数据";
             log.info("查询MySQL中的数据: " + value);
             jedis.set(key,value);
         }
         // 3. 关闭Jedis连接
         jedis.close();
         return value;
     }
 }
 =======================
 // UserController.java
 @Controller
 public class UserController {
 
     @Autowired
     UserService userService;
 
     @RequestMapping("/getString")
     @ResponseBody
     public String getString(String key){
         return userService.getString(key);
     }
 
 }

2.5 Инструменты

 // JedisUtil.java
 @Component
 public class JedisUtil {
 
     @Autowired
     private JedisPool jedisPool;
 
     /**
      * 获取Jedis资源
      * @return
      */
     public Jedis getJedis(){
         return jedisPool.getResource();
     }
 
     /**
      * 释放Jedis连接
      */
     public void close(Jedis jedis){
         if(jedis!=null){
             jedis.close();
         }
     }
     ......
 
 }
 

2.6 Тест 2: Тип строки

Требование: Пользователь вводит данные redis, срок действия ключа 30 секунд

 // UserService.java
 public interface UserService {
 
     /**
      * 测试String类型
      * 需求:用户输入一个redis数据,该key的有效期为 30 秒
      */
     public String expireStr(String key,String value);
 }
 =======================
 // UserServiceImpl.java
 @Service
 @Log    // 相当于 Logger logger = LoggerFactory.getLogger(JedisConfig.class);
 public class UserServiceImpl implements UserService {
 
     @Autowired
     JedisPool jedisPool;
 
     @Autowired
     JedisUtil jedisUtil;
     /**
      * 测试String类型
      * 需求:用户输入一个redis数据,该key的有效期为 30 秒
      */
     public String expireStr(String key,String value){
         Jedis jedis = jedisUtil.getJedis();
 
         if(!jedis.exists(key)){
             // 1.在Redis中存入数据
             jedis.set(key,value);
             // 2.设置该值过期时间
             jedis.expire(key,30);
             log.info("将" + key + "有效时间设置为:30秒。");
         }
         // 3.查询key的有效时间
         Long time = jedis.ttl(key);
 
         jedisUtil.close(jedis);
         return "该" + key + " : " + value + "的有效时间剩余: " + time;
     }
 
 }
 =======================
 @RestController
 public class UserController {
 
     @Autowired
     UserService userService;
 
     @RequestMapping("/expireStr")
     public String expireStr(String key,String value){
 
         return userService.expireStr(key,value);
     }
 
 }

2.7 Тест 3: Тип хэша

Требование: запрашивать информацию о пользователе на основе идентификатора пользователя.

Сначала определите, существует ли он в Redis:

Если он существует, возьмите его прямо из Redis;

Если он не существует, удалите его из MySQL и сохраните в Redis.

 // User.java
 @Data
 @NoArgsConstructor
 @AllArgsConstructor
 public class User implements Serializable {
 
     private String id;
     private String name;
     private Integer age;
 
 }
 
 =======================
 public interface UserService {
 
     /**
      * 测试Hash类型
      * 需求:根据用户ID查询用户信息
      *  先判断是否在Redis中存在:
      *      如果存在,直接从Redis中取出
      *      如果不存在,从MySQL中取出,并存入Redis中
      */
     public User findById(String id);
 
 }
 =======================
 /* Hash 测试*/
 @Service
 @Log    // 相当于 Logger logger = LoggerFactory.getLogger(JedisConfig.class);
 public class UserServiceImpl implements UserService {
 
     @Autowired
     JedisPool jedisPool;
     @Autowired
     JedisUtil jedisUtil;
     
     @Override
     public User findById(String id) {
         String key = "user:" + id;  // 实体类名称:id
         Jedis jedis = jedisUtil.getJedis();
         User user = null;
 
         if(jedis.exists(key)){  // 存在
             user = new User();
             Map<String, String> map = jedis.hgetAll(key);
             user.setId(map.get("id"));
             user.setName(map.get("name"));
             user.setAge(Integer.parseInt(map.get("age")));
             log.info("===================》从Redis中查询数据");
         } else{     // 不存在
             // 从MySQL中查询数据
             user = new User(id,"xiaojian",22);
             log.info("===================》从MySQL中查询数据" + user);
             // 存入Redis
             Map<String, String> map = new HashMap<>();
             map.put("id",user.getId());
             map.put("name",user.getName());
             map.put("age",user.getAge()+"");
 
             jedis.hmset(key,map);
             log.info("===================》存入Redis中");
 
         }
 
         jedisUtil.close(jedis);
         return user;
     }
 
 }
 
 =======================
 @RestController
 public class UserController {
 
     @Autowired
     UserService userService;
 
     @RequestMapping("/findById")
     public String findById(String id){
         return userService.findById(id).toString();
     }
 
 }

3. Салат интеграции Spring Boot 2.x

Lettuce: высокоуровневый клиент Redis для потокобезопасного синхронного, асинхронного и реактивного использования, поддерживающий кластеры, Sentinel, конвейеры и кодировщики.

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

3.1 Импорт зависимостей

 <!-- 默认是lettuce客户端-->
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-data-redis</artifactId>
         </dependency>
 <!-- redis依赖 commons-pool ,这个依赖一定要加-->
         <dependency>
             <groupId>org.apache.commons</groupId>
             <artifactId>commons-pool2</artifactId>
         </dependency>

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

 spring:
   redis:
     port: 6379
     password: 2436
     host: 112.124.1.187
     lettuce:
       pool:
         max-active: 10
         max-idle: 6
         min-idle: 2
         max-wait: 1000
       shutdown-timeout: 100

3.3 Класс конфигурации

 // 添加使用RedisTemplate模板,不书写,使用Spring Boot 默认
 @Configuration
 public class RedisConfig {
 
     @Bean
     public RedisTemplate<String,Object> redisTemplate(LettuceConnectionFactory factory){
         RedisTemplate<String,Object> template = new RedisTemplate();
         template.setConnectionFactory(factory);
 
         Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
         ObjectMapper om = new ObjectMapper();
         om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
         om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
         jackson2JsonRedisSerializer.setObjectMapper(om);
 
         StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
 
         // 在使用注解@Bean返回RedisTemplate的时候,同时配置hashKey与hashValue的序列化方式
         // key 采用String的序列化方式
         template.setKeySerializer(stringRedisSerializer);
         // value 采用jackson的序列化方式
         template.setValueSerializer(jackson2JsonRedisSerializer);
         
         // hash 的key 也采用String的序列化方式
         template.setHashKeySerializer(stringRedisSerializer);
         // hash 的value采用jackson的序列化方式
         template.setHashValueSerializer(jackson2JsonRedisSerializer);
 
         template.afterPropertiesSet();
 
         return template;
     }
 }

3.3.1 Проблемы с конфигурацией

     @Bean
     public RedisTemplate<String,Object> redisTemplate(LettuceConnectionFactory factory){
         RedisTemplate<String,Object> template = new RedisTemplate();
         template.setConnectionFactory(factory);
 
         // ****** 改2 ******
         GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
 
         // Jackson 格式
         Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
         ObjectMapper om = new ObjectMapper();
         om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
         // 方法过期,改 1 时注释掉这里,正常 或 改 2 时使用
 //        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
         // ****** 改1 ******,其他情况下注释掉
         om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance);
         jackson2JsonRedisSerializer.setObjectMapper(om);
 
         // String 类型格式
         StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
         // 在使用注解@Bean返回RedisTemplate的时候,同时配置hashKey与hashValue的序列化方式
         // key 采用String的序列化方式
         template.setKeySerializer(stringRedisSerializer);
         // value 采用jackson的序列化方式,使用 ****** 改 2 ****** 对象
         template.setValueSerializer(genericJackson2JsonRedisSerializer);
 
         // hash 的key 也采用String的序列化方式
         template.setHashKeySerializer(stringRedisSerializer);
         // hash 的value采用jackson的序列化方式,使用 ****** 改 2 ****** 对象
         template.setHashValueSerializer(genericJackson2JsonRedisSerializer);
 
         template.afterPropertiesSet();
 
         return template;
     }

хеш-тип данных

  1. Исходный файл конфигурации дает результат (как отображается RedisDesktopManager):
 ["com.xiaojian.pojo.User",{"id":"1103","name":"修心","age":22}]
  1. изменить 1
 {"id":"1105","name":"修心","age":22}
  1. изменить 2
 {"@class":"com.xiaojian.pojo.User","id":"1106","name":"修心","age":22}

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

 ========
 @Service
 @Slf4j
 public class UserServiceImpl {
 
     @Autowired
     private RedisTemplate<String,Object> redisTemplate;
 
 
     public String getString(){
         System.out.println(redisTemplate);
         log.info("RedisTemplate--------->测试");
 
         return null;
     }
 }
 
 ==========
 @SpringBootTest
 class BootLettuceApplicationTests {
 
     @Autowired
     private UserServiceImpl userService;
 
     @Test
     void contextLoads() {
         userService.getString();
     }
 
 }

PS: Китайский запрос в linux отображается в шестнадцатеричном виде.Вы можете войти в клиент, добавив --raw после redis-cli

 [root@xiaojian bin]# ./redis-cli -a 2436 --raw

3.5 Тест 1: Тип строки

Требование: пользователь вводит ключ, чтобы сначала определить, существуют ли данные в Redis. Если они существуют, запросите их в Redis и верните. Если они не существуют, выполните запрос в базе данных MySQL, назначьте результат Redis и верните

 @Service
 @Slf4j
 public class UserServiceImpl {
 
     @Autowired
     private RedisTemplate<String,Object> redisTemplate;
 
     /**
      * Lettuce --> RedisTemplate 进一步的封装
      * RedisTemplate 的方法和命令不一样
      *
      * Redis String 类型
      * 需求:用户输入一个key
      * 先判断Redis中是否存在该数据
      *  如果存在,在Redis中进行查询,并返回
      *  如果不存在,在MySQL数据库查询,将结果赋给Redis,并返回
      *
      * @return
      */
     public String getString(String key){
 
         String val = "";
         if(redisTemplate.hasKey(key)){ // exist
             val = (String) redisTemplate.opsForValue().get(key);
             log.info("-----> 从Redis中查询出数据:" + val);
         } else{
             val = "MYSQL中查询出来的数据";
             log.info("-----> 从MySQL中查询出的数据:" + val);
             redisTemplate.opsForValue().set(key,val);
             log.info("-----> 把从MySQL中查询出来的数据存入Redis");
         }
         return val;
     }
 }
 =======================
 @SpringBootTest
 class BootLettuceApplicationTests {
 
     @Autowired
     private UserServiceImpl userService;
 
     @Test
     void contextLoads() {
         String result = userService.getString("lettuce");
         System.out.println(result);
     }
 
 }

3.6 Тест 2: Тип строки

Требование: Пользователь вводит данные redis, срок действия ключа 30 секунд

 @Service
 @Slf4j
 public class UserServiceImpl {
 
     @Autowired
     private RedisTemplate<String,Object> redisTemplate;
 
     /**
      * 测试 String 类型
      * 需求:用户输入一个redis数据,该key的有效期为20小时
      * @return
      */
     public void expireStr(String key,String value){
 
         redisTemplate.opsForValue().set(key,value);
         // 定时,可以指定单位:天,时,分,秒
         redisTemplate.expire(key,20, TimeUnit.HOURS);
 
     }
 }
 
 ================
 @SpringBootTest
 class BootLettuceApplicationTests {
 
     @Autowired
     private UserServiceImpl userService;
 
     @Test
     void t2() {
         userService.expireStr("timeout","午时已到!");
     }
 
 }

3.7 Тест 3: Тип хеша (id должен быть строкой)

Требование: запрашивать информацию о пользователе на основе идентификатора пользователя.

Сначала определите, существует ли он в Redis:

Если он существует, возьмите его прямо из Redis;

Если он не существует, удалите его из MySQL и сохраните в Redis.

 // 首先,在 RedisConfig 类中添加hash的序列化配置
 ...
         // hash 的key 也采用String的序列化方式
         template.setHashKeySerializer(stringRedisSerializer);
         // hash 的value采用jackson的序列化方式
         template.setHashValueSerializer(jackson2JsonRedisSerializer);
 ...
     
 ================
 @Service
 @Slf4j
 public class UserServiceImpl {
 
     @Autowired
     private RedisTemplate<String,Object> redisTemplate;
 
     /**
      * 测试 Hash
      * @param id
      * @return
      *
      * 根据 Id 查询用户对象信息
      * 先判断Redis中是否存在该key
      * 如果不存在,查询MySQL 数据库,并将结果添加到 Redis 中,并返回
      * 如果存在,直接将结果在Redis查询并返回
      */
     public User findById(String id){
 
         User user = null;
 
         if(redisTemplate.opsForHash().hasKey("user",id)){
             log.info("----->从 Redis 中取出数据!");
             user = (User)redisTemplate.opsForHash().get("user",id);
         } else{
             // 从 MySQL 中取出数据
             user = new User();
             user.setId(id);
             user.setName("修心");
             user.setAge(22);
             log.info("----->从 MySQL 中取出数据");
 
             /**
                 @ param h 用户实体,user
                 @ param hk 用户主键
                 @ param hv 整个对象
              */
             redisTemplate.opsForHash().put("user",id,user);
             log.info("----->将 map 数据存入 Redis中");
         }
              return user;
     }
 }
 
 ========================
 @SpringBootTest
 class BootLettuceApplicationTests {
 
     @Autowired
     private UserServiceImpl userService;
 
     @Test
     void t3() {
         User user = userService.findById("1143");
         System.out.println(user);
     }
 
 }

ПС: вопрос

Проблема 1: Есть много одинаковых строк ---- > извлеченных

Решение 1. Класс инструментов

Решение 2. Entity Bean объявляет метод, возвращающий строку этого класса.

Проблема 2: Проблема принуждения и повторная запись в течение длительного времени redisTemplate.opsForHash()

Решение: объявите переменную над бизнес-классом и замените redisTemplate.opsForHash() именем переменной.

     @Resource(name = "redisTemplate")
     private ValueOperations<String, String> redisString;
 
     @Resource(name = "redisTemplate")
     private HashOperations<String, String, User> redisHash;    // K:"user"; HK:"ID"; HV: Object

4. Общие приложения Redis

4.1 Функция проверки мобильного телефона

нужно:

Пользователь вводит номер мобильного телефона на клиенте и нажимает «Отправить», чтобы сгенерировать четырехзначный код, действительный в течение 60 секунд.

Введите код подтверждения, нажмите «Подтвердить», верните сообщение об успешном или неудачном завершении, и каждый номер мобильного телефона может быть проверен только 3 раза в течение 5 минут. и дать соответствующую информацию

4.2 Ограничение входа в систему

нужно:

Пользователю разрешается ввести неправильный пароль только 5 раз в течение 2 минут;

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