Redis details - SpringBoot integrates the use of redis, RedisTemplate and annotation

This article mainly talks about the use of Redis, how to integrate with SpringBoot project, and how to use annotation and RedisTemplate to realize caching. Finally, we will give a case that Redis is used to implement distributed locks in the second kill system.

For more practical application scenarios of Redis, please pay attention to the open source project coderiver

Project address: https://github.com/cachecats/...

1, NoSQL overview

What is NoSQL?

NoSQL(NoSQL = Not Only SQL), which means "not just SQL ”, generally refers to non relational database.

Why do I need NoSQL?

With the Internet web2.0 With the rise of websites, traditional relational databases are dealing with web2 0 websites, especially those with large scale and high concurrency SNS Type of web2 0 pure Dynamic network The station has been unable to meet its needs and exposed many insurmountable problems, while the non relational database has developed very rapidly due to its own characteristics. The generation of NoSQL database is to solve the challenges brought by large-scale data sets and multiple data types, especially the problems of big data application-- Baidu Encyclopedia

Four categories of NoSQL database

  • Key value storage
  • Column storage
  • Document database
  • Graphic database
classificationRelated productsTypical applicationdata modeladvantageshortcoming
Key valueTokyo, Cabinet/Tyrant,Redis,Voldemort,Berkeley DBContent caching is mainly used to deal with the high access load of a large amount of dataA series of key value pairsQuick queryThe stored data is not structured
Column storage databaseCassandra, HBase, RiakDistributed file systemIt is stored in column clusters to store the same column of data togetherFast search speed, strong scalability and easier distributed expansionRelatively limited functions
Document databaseCouchDB, MongoDBWeb application (similar to key value, value is structured)A series of key value pairsThe data structure requirements are not strictThe query performance is not high, and there is a lack of unified query syntax
Graph databaseNeo4J, InfoGrid, Infinite GraphSocial networks, recommendation systems, etc. Focus on building relationship mapsFigure structureUsing graph structure correlation algorithmThe results can only be obtained by calculating the whole graph, so it is not easy to make a distributed cluster scheme

Features of NoSQL

  • Easy to expand
  • Flexible data model
  • Large amount of data, high performance
  • High availability

2, Redis overview

Redis application scenario

  • cache
  • Task queue
  • Website access statistics
  • App Leaderboard
  • Data expiration processing
  • session separation in distributed cluster architecture

Redis installation

There are many Redis installation tutorials on the Internet. I won't talk more about it here. Just talk about the installation method of Docker:

Docker installation and running Redis

docker run -d -p 6379:6379 redis:4.0.8

If you want to start Redis service in the future, open the command line and enter the following command.

redis-server

Import dependency before use

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

3, Redis cache is used for annotation

There are two pre steps to using caching

  1. In POM XML import dependency

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
  2. Annotate @ enabling class

    @SpringBootApplication
    @EnableCaching
    public class SellApplication {
        public static void main(String[] args) {
            SpringApplication.run(SellApplication.class, args);
        }
    }

Common annotations are as follows

  • @Cacheable

    The attributes are shown in the figure below

 

It is used to query and add cache, return the return value of this method at the first query, and save data to Redis server.

Later, the method is called to check if there is data from Redis, if there is a direct return to the Redis cache data, instead of executing the code in the method. If not, execute the code in the method body normally.

The value or cacheNames attribute is used as the key, and the key attribute can be regarded as the sub key of value. A value can be composed of multiple keys, and different values exist in the Redis server.

After verification, value and cacheNames have the same function. They both identify the primary key. Two attributes cannot be defined at the same time, only one can be defined, otherwise an error will be reported.

condition and unless are conditions, which will be used later. Several other attributes are not commonly used. In fact, I don't know how to use them

  • @CachePut

    Update the value of the corresponding key in Redis. Property is the same as @ Cacheable

  • @CacheEvict

    Delete the value of the corresponding key in Redis.

3.1 add cache

Add the annotation @ Cacheable(cacheNames = "product", key = "123") on the method that needs to be cached,

cacheNames and key must be filled in. If the key is not filled in, the default key is the current method name. When updating the cache, the update will fail due to different method names.

For example, add cache on the order list

    @RequestMapping(value = "/list", method = RequestMethod.GET)
    @Cacheable(cacheNames = "product", key = "123")
    public ResultVO list() {

        // 1. Query all goods on the shelves
        List<ProductInfo> productInfoList = productInfoService.findUpAll();

        // 2. Query category (one-time query)
        //Get all types of goods on the shelves with the features of java8
        List<Integer> categoryTypes = productInfoList.stream().map(e -> e.getCategoryType()).collect(Collectors.toList());
        List<ProductCategory> productCategoryList = categoryService.findByCategoryTypeIn(categoryTypes);

        List<ProductVO> productVOList = new ArrayList<>();
        //Data assembly
        for (ProductCategory category : productCategoryList) {
            ProductVO productVO = new ProductVO();
            //Attribute copy
            BeanUtils.copyProperties(category, productVO);
            //Add items with matching types
            List<ProductInfoVO> productInfoVOList = new ArrayList<>();
            for (ProductInfo productInfo : productInfoList) {
                if (productInfo.getCategoryType().equals(category.getCategoryType())) {
                    ProductInfoVO productInfoVO = new ProductInfoVO();
                    BeanUtils.copyProperties(productInfo, productInfoVO);
                    productInfoVOList.add(productInfoVO);
                }
            }
            productVO.setProductInfoVOList(productInfoVOList);
            productVOList.add(productVO);
        }

        return ResultVOUtils.success(productVOList);
    }

The following errors may be reported

 

Object is not serialized. Just let the object implement the Serializable method

@Data
public class ProductVO implements Serializable {
    
    private static final long serialVersionUID = 961235512220891746L;

    @JsonProperty("name")
    private String categoryName;

    @JsonProperty("type")
    private Integer categoryType;

    @JsonProperty("foods")
    private List<ProductInfoVO> productInfoVOList ;
}

To generate a unique id, there is a plug-in in the IDEA: GenerateSerialVersionUID, which is more convenient.

Restart the project, access the order list, and view the Redis cache in rdm. product::123 indicates that the cache is successful.

 

3.2 update cache

Annotate the method that needs to update the cache: @ CachePut(cacheNames = "prodcut", key = "123")

be careful

  1. cacheNames and key s must be consistent with those in @ Cacheable() before they can be updated correctly.
  2. @The return values of the methods annotated by CachePut() and @ Cacheable() should be consistent

3.3 delete cache

Add a comment on the method to delete the cache: @ CacheEvict(cacheNames = "prodcut", key = "123"). After executing this method, the corresponding records in Redis will be deleted.

3.4 other common functions

  1. cacheNames can also be written uniformly on classes, @ CacheConfig(cacheNames = "product"), and there is no need to write on specific methods.

    @CacheConfig(cacheNames = "product")
    public class BuyerOrderController {
        @PostMapping("/cancel")
        @CachePut(key = "456")
        public ResultVO cancel(@RequestParam("openid") String openid,
                               @RequestParam("orderId") String orderId){
            buyerService.cancelOrder(openid, orderId);
            return ResultVOUtils.success();
        }
    }
  2. Key can also be dynamically set as a parameter of the method

    @GetMapping("/detail")
    @Cacheable(cacheNames = "prodcut", key = "#openid")
    public ResultVO<OrderDTO> detail(@RequestParam("openid") String openid,
                                 @RequestParam("orderId") String orderId){
        OrderDTO orderDTO = buyerService.findOrderOne(openid, orderId);
        return ResultVOUtils.success(orderDTO);
    }

    If the parameter is an object, you can also set an attribute of the object as key. For example, one of the parameters is the user object, and the key can be written as key="#user.id"

  3. Caching can also set conditions.

    Set to cache when the length of openid is greater than 3

    @GetMapping("/detail")
    @Cacheable(cacheNames = "prodcut", key = "#openid", condition = "#openid.length > 3")
    public ResultVO<OrderDTO> detail(@RequestParam("openid") String openid,
                                     @RequestParam("orderId") String orderId){
        OrderDTO orderDTO = buyerService.findOrderOne(openid, orderId);
        return ResultVOUtils.success(orderDTO);
    }

    You can also specify unless, that is, caching when the condition is not true# result represents the return value, which means that it is not cached when the return code is not equal to 0, that is, it is cached when it is equal to 0.

    @GetMapping("/detail")
    @Cacheable(cacheNames = "prodcut", key = "#openid", condition = "#openid.length > 3", unless = "#result.code != 0")
    public ResultVO<OrderDTO> detail(@RequestParam("openid") String openid,
                                     @RequestParam("orderId") String orderId){
        OrderDTO orderDTO = buyerService.findOrderOne(openid, orderId);
        return ResultVOUtils.success(orderDTO);
    }

4, RedisTemplate uses Redis cache

Different from the annotation method, the annotation method can be zero configured. It can be used by introducing dependency and adding @ EnableCaching annotation on the startup class; RedisTemplate is more troublesome and needs to be configured.

4.1 Redis configuration

The first step is to introduce dependency and add @ EnableCaching annotation on the startup class.

Then in application Redis is configured in the YML file

spring:
  redis:
    port: 6379
    database: 0
    host: 127.0.0.1
    password:
    jedis:
      pool:
        max-active: 8
        max-wait: -1ms
        max-idle: 8
        min-idle: 0
    timeout: 5000ms

Then write a redisconfig Java configuration class

package com.solo.coderiver.user.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
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;

import java.net.UnknownHostException;


@Configuration
public class RedisConfig {

    @Bean
    @ConditionalOnMissingBean(name = "redisTemplate")
    public RedisTemplate<String, Object> redisTemplate(
            RedisConnectionFactory redisConnectionFactory)
            throws UnknownHostException {

        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);

        RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
        template.setConnectionFactory(redisConnectionFactory);
        template.setKeySerializer(jackson2JsonRedisSerializer);
        template.setValueSerializer(jackson2JsonRedisSerializer);
        template.setHashKeySerializer(jackson2JsonRedisSerializer);
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }

    @Bean
    @ConditionalOnMissingBean(StringRedisTemplate.class)
    public StringRedisTemplate stringRedisTemplate(
            RedisConnectionFactory redisConnectionFactory)
            throws UnknownHostException {
        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }
}

Redis configuration is completed.

4.2 data structure type of redis

Redis can store mappings between keys and five different data structure types: String, List, Set, Hash and Zset.

The following is a brief introduction to these five data structure types:

structure type Value stored by structureStructure reading and writing ability
StringIt can be a string, integer or floating point numberPerform operations on the whole string or part of the string; Objects and floating-point numbers are incremented or decremented
ListA linked list. Each node in the linked list contains a stringPush or pop up elements from both ends of the linked list; Trim the linked list according to the offset; Reading single or multiple elements; Find or remove elements based on values
SetUnordered collection containing strings, and each string contained is unique and differentAdd, get and remove individual elements; Check whether an element exists in a collection; Calculate intersection, union and difference sets; Show off random elements from a collection
HashUnordered hash table containing key value pairsAdd, obtain and remove a single key value pair; Get all key value pairs
ZsetAn ordered mapping between a string member and a floating-point score. The order of elements is determined by the size of the scoreAdd, obtain and delete a single element; Get elements according to score range or members

4.3 StringRedisTemplate and RedisTemplate

RedisTemplate defines operations for five data structures

  • redisTemplate.opsForValue();

    Operation string

  • redisTemplate.opsForHash();

    Operation hash

  • redisTemplate.opsForList();

    Operation list

  • redisTemplate.opsForSet();

    Operation set

  • redisTemplate.opsForZSet();

    Operation order set

If you want to manipulate strings, it is recommended to use StringRedisTemplate.

The difference between StringRedisTemplate and RedisTemplate

  1. StringRedisTemplate inherits RedisTemplate.
  2. RedisTemplate is a generic class, while StringRedisTemplate is not.
  3. StringRedisTemplate can only operate on key value pairs with key=String and value=String. RedisTemplate can operate on any type of key value pairs.
  4. Their serialization methods are different, but in the end, they all get a byte array, which leads to the same goal. StringRedisTemplate uses the StringRedisSerializer class; RedisTemplate uses the JdkSerializationRedisSerializer class. In deserialization, one gets String and the other gets Object
  5. The data of the two are not in common. StringRedisTemplate can only manage the data in StringRedisTemplate, and RedisTemplate can only manage the data in RedisTemplate.

4.4 use in the project

Where Redis needs to be used, inject it with @ Autowired

@Autowired
RedisTemplate redisTemplate;
 
@Autowired
StringRedisTemplate stringRedisTemplate;

For the time being, only the Hash structures of StringRedisTemplate and RedisTemplate are used in the project. The StringRedisTemplate is relatively simple and no code is pasted. The following is only an example of the operation of Hash.

About the detailed usage of RedisTemplate, an article has been very detailed and good. I don't think it's necessary to write it again. Portal

Using RedisTemplate to operate Hash

package com.solo.coderiver.user.service.impl;

import com.solo.coderiver.user.dataobject.UserLike;
import com.solo.coderiver.user.dto.LikedCountDTO;
import com.solo.coderiver.user.enums.LikedStatusEnum;
import com.solo.coderiver.user.service.LikedService;
import com.solo.coderiver.user.service.RedisService;
import com.solo.coderiver.user.utils.RedisKeyUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

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

@Service
@Slf4j
public class RedisServiceImpl implements RedisService {

    @Autowired
    RedisTemplate redisTemplate;

    @Autowired
    LikedService likedService;

    @Override
    public void saveLiked2Redis(String likedUserId, String likedPostId) {
        String key = RedisKeyUtils.getLikedKey(likedUserId, likedPostId);
        redisTemplate.opsForHash().put(RedisKeyUtils.MAP_KEY_USER_LIKED, key, LikedStatusEnum.LIKE.getCode());
    }

    @Override
    public void unlikeFromRedis(String likedUserId, String likedPostId) {
        String key = RedisKeyUtils.getLikedKey(likedUserId, likedPostId);
        redisTemplate.opsForHash().put(RedisKeyUtils.MAP_KEY_USER_LIKED, key, LikedStatusEnum.UNLIKE.getCode());
    }

    @Override
    public void deleteLikedFromRedis(String likedUserId, String likedPostId) {
        String key = RedisKeyUtils.getLikedKey(likedUserId, likedPostId);
        redisTemplate.opsForHash().delete(RedisKeyUtils.MAP_KEY_USER_LIKED, key);
    }

    @Override
    public void incrementLikedCount(String likedUserId) {
        redisTemplate.opsForHash().increment(RedisKeyUtils.MAP_KEY_USER_LIKED_COUNT, likedUserId, 1);
    }

    @Override
    public void decrementLikedCount(String likedUserId) {
        redisTemplate.opsForHash().increment(RedisKeyUtils.MAP_KEY_USER_LIKED_COUNT, likedUserId, -1);
    }

    @Override
    public List<UserLike> getLikedDataFromRedis() {
        Cursor<Map.Entry<Object, Object>> cursor = redisTemplate.opsForHash().scan(RedisKeyUtils.MAP_KEY_USER_LIKED, ScanOptions.NONE);
        List<UserLike> list = new ArrayList<>();
        while (cursor.hasNext()) {
            Map.Entry<Object, Object> entry = cursor.next();
            String key = (String) entry.getKey();
            //Separate likedUserId and likedPostId
            String[] split = key.split("::");
            String likedUserId = split[0];
            String likedPostId = split[1];
            Integer value = (Integer) entry.getValue();

            //Assemble into UserLike object
            UserLike userLike = new UserLike(likedUserId, likedPostId, value);
            list.add(userLike);

            //Delete from Redis after saving to list
            redisTemplate.opsForHash().delete(RedisKeyUtils.MAP_KEY_USER_LIKED, key);
        }

        return list;
    }

    @Override
    public List<LikedCountDTO> getLikedCountFromRedis() {
        Cursor<Map.Entry<Object, Object>> cursor = redisTemplate.opsForHash().scan(RedisKeyUtils.MAP_KEY_USER_LIKED_COUNT, ScanOptions.NONE);
        List<LikedCountDTO> list = new ArrayList<>();
        while (cursor.hasNext()) {
            Map.Entry<Object, Object> map = cursor.next();
            //Store the number of likes in LikedCountDT
            String key = (String) map.getKey();
            LikedCountDTO dto = new LikedCountDTO(key, (Integer) map.getValue());
            list.add(dto);
            //Delete this record from Redis
            redisTemplate.opsForHash().delete(RedisKeyUtils.MAP_KEY_USER_LIKED_COUNT, key);
        }
        return list;
    }
}

5, Redis implements distributed locks

After talking about the basic operation, let's talk about the practical application. Redis is used to realize the distributed lock.

Before implementing distributed locks, first look at two Redis commands:

  • SETNX

    Set the key value to value. If the key does not exist, it is equivalent in this case SET Command. When the key exists, do nothing. SETNX is short for "SET if Not eXists".

    Return value

    Integer reply , specific value:

    • 1 if the key is set
    • 0 if the key is not set

example

redis> SETNX mykey "Hello"
(integer) 1
redis> SETNX mykey "World"
(integer) 0
redis> GET mykey
"Hello"
redis> 
  • GETSET

    Automatically map the key to value and return the value corresponding to the original key. If the key exists but the corresponding value is not a string, an error is returned.

    Design mode

    GETSET Can and INCR Used together to realize the counting function supporting reset. For example: whenever an event occurs, a program will call INCR Add 1 to key mycounter, but sometimes we need to get the value of the counter and automatically reset it to 0. This can be achieved through GETSET mycounter "0":

    INCR mycounter
    GETSET mycounter "0"
    GET mycounter

    Return value

    bulk-string-reply : returns the previous old value. If the previous Key does not exist, nil will be returned.

    example

    redis> INCR mycounter
    (integer) 1
    redis> GETSET mycounter "0"
    "1"
    redis> GET mycounter
    "0"
    redis>

These two commands correspond to setIfAbsent and getAndSet in java

Implementation of distributed lock:

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

@Component
@Slf4j
public class RedisLock {

    @Autowired
    StringRedisTemplate redisTemplate;

    /**
     * Lock
     * @param key
     * @param value Current time + timeout
     * @return
     */
    public boolean lock(String key, String value){
        if (redisTemplate.opsForValue().setIfAbsent(key, value)){
            return true;
        }

        //Solve deadlock, and when multiple threads come at the same time, only one thread will get the lock
        String currentValue = redisTemplate.opsForValue().get(key);
        //If expired
        if (!StringUtils.isEmpty(currentValue) &&
                Long.parseLong(currentValue) < System.currentTimeMillis()){
            //The time when the last lock was acquired
            String oldValue = redisTemplate.opsForValue().getAndSet(key, value);
            if (StringUtils.isEmpty(oldValue) && oldValue.equals(currentValue)){
                return true;
            }
        }

        return false;
    }

    /**
     * Unlock
     * @param key
     * @param value
     */
    public void unlock(String key, String value){

        try {
            String currentValue = redisTemplate.opsForValue().get(key);
            if (!StringUtils.isEmpty(currentValue) && currentValue.equals(value)){
                redisTemplate.opsForValue().getOperations().delete(key);
            }
        }catch (Exception e){
            log.error("[redis [lock] unlocking failed, {}", e);
        }
    }
}

use:

/**
 * Simulated second kill
 */
public class SecKillService {

    @Autowired
    RedisLock redisLock;

    //Timeout 10s
    private static final int TIMEOUT = 10 * 1000;

    public void secKill(String productId){
        long time = System.currentTimeMillis() + TIMEOUT;
        //Lock
        if (!redisLock.lock(productId, String.valueOf(time))){
            throw new SellException(101, "There are too many people. Try again later~");
        }

        //Specific second kill logic

        //Unlock
        redisLock.unlock(productId, String.valueOf(time));
    }
}

For more specific usage scenarios of Redis, please pay attention to the open source project CodeRiver, which is committed to building a full platform and full stack boutique open source project.

coderiver is a platform for programmers and designers to collaborate on projects. Whether you are a front-end, back-end or mobile developer, designer or product manager, you can publish projects on the platform and cooperate with like-minded partners to complete the project.

Codeiver River code is similar to the programmer's Inn, but its main purpose is to facilitate the technical exchange between talents in various subdivided fields, grow together and cooperate with many people to complete the project. Money transactions are not involved for the time being.

It is planned to make a full platform full stack project including pc (Vue, React), mobile H5 (Vue, React), ReactNative hybrid development, Android native, wechat applet and java back-end. Welcome to pay attention.

Project address: https://github.com/cachecats/...

Keywords: Spring Boot

Added by majiclab on Sun, 30 Jan 2022 03:22:14 +0200