First, cache concept knowledge

1. What is the cache

We often hear the term cache in our daily life, such as browser clean cache, processor cache size, disk cache and so on. After classification, caches can be divided into:

  • Hardware cache: generally refers to the cache interval of the CPU, hard disk and other components on the machine. Generally, the memory is used as a transfer area to exchange information through the memory, reduce the system load and provide transmission efficiency.
  • Client cache: Generally refers to some applications, such as a browser, mobile phone App, video buffer, etc., are in a data load after the temporary storage of data to a local, when to visit again to check whether there is in the local cache, there don’t have to go to a remote to pull, but directly read cache data, so that to reduce the remote server pressure and speed up the load.
  • Server cache: Generally, it refers to the remote server. Considering the large number of requests from the client and some data requests, the hotspot data often reads data from the database, which causes pressure on the database. In addition, there is a certain delay in I/O and network, and the response to the client is slow. Therefore, in some data that do not consider real time, these data are often stored in memory (memory speed is very fast), when the request, can directly read the data in memory and timely response, learn from Redis actual learning notes.

2. Why cache

With cache, the main solution is high performance and high concurrency and reduce database pressure. Cache essence is to store the data in memory, when the data did not change nature, we should try to avoid directly connect to the database query, because of the high concurrency is likely to database collapse, but should go to read data in the cache, only the cache is not to find and then go to a database query, thus greatly reducing the number of read and write database, Increases the performance of the system and the amount of concurrency it can provide.

3. Advantages and disadvantages of caching

Advantages:

  • Speed up the response
  • This reduces the database read operations and reduces the database pressure.

Disadvantages:

  • The memory capacity is smaller than that of hard disks.
  • The data in the cache may be inconsistent with the data in the database.
  • Data stored in memory may be lost because of memory power outages that wipe out data.

Second, Redis concept knowledge

1. What is Redis

Redis is a high performance key-value database, it is completely open source free, and Redis is a NoSQL type database, is to solve a series of problems such as high concurrency, high expansion, big data storage and other database solutions, is a non-relational database. However, it can not replace the relational database, can only be used as a specific environment under the extension.

2. Why use Redis as cache

  • Support for high availability: Redis supports master slave master slave, Sentinal Sentinel mode and Cluster mode, which greatly ensures stable operation and high availability of Redis.
  • Support for multiple data structures: Redis not only supports simple Key/Value types of data, but also provides the storage of list, set, zset, hash and other data structures.
  • Support data persistence: Data in memory can be persisted in disk, in the event of downtime or failure to restart, can be reloaded into such as Redis, so as to not or reduce data loss.
  • There are many tools and plug-ins to support it: Redis is already widely used in the industry and has become the preferred target for caching, so it is supported by many languages and tools and can be easily used with simple operations.

3. Data types supported by Redis

The types of data structures Redis supports include:

  • String (string)
  • Hash table
  • List
  • Set
  • Ordered set (Zset)

To ensure efficient reads, Redis stores data objects in memory, which can periodically write updated data to disk files. It also provides intersection and union, as well as some operations for sorting in different ways.

3. Problems that may be encountered after caching

1. Cache penetration

Cache penetration: The system queries data that does not exist. If the cache does not match, the system queries the data from the database. If no data is found, the system does not write the data into the cache.

Cache penetration has several workarounds:

  • Cache null values. When the query object from DB is empty, the null value is also stored in the cache. The specific value needs to use a special identifier to distinguish it from the real cache data, and set its expiration time to a short time.
  • Using bloom filter, bloom filter can determine a key doesn’t exist certainly (does not guarantee that there must be, because the bloom filter structure reason, cannot be deleted, but the old value may be replaced by a new value, and delete the old value after it may determine its still possible), on the basis of the cache, build bloom filter data structures, Stores the corresponding key in the Bloom filter. If the key exists, the corresponding value is null.

2. Cache breakdown

Cache breakdown: a key is very hot, very frequently accessed, in the case of centralized high concurrent access, when the key fails at the moment, a large number of requests will break through the cache and directly request the database, just like cutting a hole in a barrier.

Cache breakdown several solutions:

  • Setting level 2 cache or setting hotspot cache to never expire depends on the actual situation.
  • Use mutex lock, in the execution process, if the cache expires, then obtain the distributed lock first, load data from the database in the execution, if the data is found in the cache, not continue to have the action, in this process can ensure that only one thread operation database, avoid a large number of requests to the database.

Cache avalanche

Cache avalanche: When the cache server restarts or a large number of caches fail in a certain period of time, the failure will bring great pressure to the back-end system (such as the DB), causing the backend failure of the database and causing the application server avalanche.

Cache avalanche several solutions:

  • Cache components are designed for high availability. Cache high availability refers to the high availability of components that store cache, preventing problems such as single point of failure, machine failure, and equipment room downtime. Redis Sentinel and Redis Cluster, for example, are both highly available.
  • Request traffic limiting and service circuit breaker downgrading mechanism to limit the number of service requests and fast circuit breaker downgrading when service is unavailable.
  • Set a random distribution of cache expiration time to avoid cache invalidation at the same time.
  • The cache policy is updated periodically. Data with low real-time requirements is updated periodically.

4. Cache consistency

Using caches can lead to data inconsistency issues as follows:

  • More familiar database succeeded -> update cache failed -> Data inconsistent
  • Update cache succeeded -> update database failed -> Inconsistent data
  • Update database successful -> flush cache failed -> Data inconsistent
  • Flush cache successful -> update database failed -> query cache MIS

Therefore, when using the cache, you should consider whether the cached data has consistency requirements based on the actual situation.

How to combine SpringBoot with Redis cache

1. Mavne introduces dependencies

  • Spring – the boot – starter – data – redis:
  • Commons – pool2:

2. Set Redis parameters

Add configuration parameters to connect to Redis in the application file

Redis single machine configuration:

Redis Sentry configuration:

Redis cluster configuration:

3. Configure the Spring cache manager

@Configuration
public class RedisConfig {

    /** * Configure the cache manager *@param Factory Redis Thread-safe connection factory *@return The cache manager */
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory) {
        // Generate two default configurations. The cache can be customized using the Config object
        RedisCacheConfiguration cacheConfig = RedisCacheConfiguration.defaultCacheConfig()
                // Set the expiration time to 10 minutes
                .entryTtl(Duration.ofMinutes(10))
                // Set the cache prefix
                .prefixKeysWith("cache:user:")
                // Disable caching null values
                .disableCachingNullValues()
                // Set key serialization
                .serializeKeysWith(keyPair())
                // Set value serialization
                .serializeValuesWith(valuePair());
        // Return the Redis cache manager
        return RedisCacheManager.builder(factory)
                            .withCacheConfiguration("user", cacheConfig).build();
    }

    /** * configure key serialization *@return StringRedisSerializer* /
    private RedisSerializationContext.SerializationPair<String> keyPair() {
        return RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer());
    }

    / * * * configuration values serialization, use GenericJackson2JsonRedisSerializer replace the default serialization *@return GenericJackson2JsonRedisSerializer* /
    private RedisSerializationContext.SerializationPair<Object> valuePair() {
        return RedisSerializationContext.SerializationPair.fromSerializer(newGenericJackson2JsonRedisSerializer()); }}Copy the code

4. Use SpringCache annotations in the service

@Service
@CacheConfig(cacheNames = "user")
public class UserServiceImpl implements UserService {

    /** * New user */
    public User addUser(User user){... }/** * Query user */
    @Cacheable(key = "#username")
    public User getUserByUsername(String username){... }/** * Update user */
    @CachePut(key = "#user.username")
    public User updateUser(User user){... }/** * Delete user */
    @CacheEvict(key = "#username")
    public void deleteByUsername(String username){... }}Copy the code

Notes:

  • @cacheconfig: Specifies the cache name on the class. This name is the same as the cache name in the “Cache Manager” section above.
  • @cacheable: Applied to a method to cache the result returned by the method. If the cache already exists, it is directly fetched from the cache. The key of the cache can be specified from the input parameter, and the value of the cache is the value returned by the method.
  • @cachePUT: Applied to a method. The cache is readded each time, regardless of whether the cache exists. The key of the cache can be specified from the input parameter.
  • @cacheevict: Applied to a method to clear the cache
  • @caching: Applies to a method to set multiple caches at a time.

Common configuration parameters in the above annotations:

  • Value: cache manager configured in the name of the cache, it can be understood as the concept of a group, the cache manager can have multiple cache configuration, each has a name, similar to the group name, this can configure this value, select which one to use the name of the cache, the cache configuration will be applied after the name of the corresponding configuration.
  • Key: The key of the cache, which can be empty. If specified, it is written as an SpEL expression. If not specified, all parameters of the method are combined by default.
  • Condition: The condition of the cache, which can be empty, written in SpEL, returns true or false, and only true is cached.
  • Unless: The condition that is not cached, like condition, is written in SpEL and returns true or false. If true, it is not cached.

5, start class add enable caching annotations

  • @enablecaching: Applies to a class to enable the annotation function.

5. Insufficient SpringCache operation cache

Spring Cache is convenient, but it has some limitations because it names the key according to the request parameter and sets the value according to the return reference. In many cases, naming and operating within methods is limited. If you need flexibility in caching, you can use the spring-data-redis package to manually manipulate keys and values in your code without the annotations provided by SpringCache.

  • opsForValue().set(String key, String value);
  • opsForValue().get(String key);

Also often to batch set, read cache, can use:

  • opsForValue().multiSet(Map map);
  • opsForValue().multiGet(List list);

SpringBoot + SpringCache + Redis sample project

The following is a simple SpringBoot project, used to add, delete, modify and check the user, here uses SpringCache to simulate the cache of data, as shown in the following example:

1. Mavne introduces dependencies

Maven introduces SpringBoot and Redis dependencies because they are used

<? xml version="1.0" encoding="UTF-8"? ><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.2. RELEASE</version>
    </parent>

    <groupId>mydlq.club</groupId>
    <artifactId>springboot-redis-example</artifactId>
    <version>0.0.1</version>
    <name>springboot-redis-example</name>
    <description>Demo project for Spring Boot Redis</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <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>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>
Copy the code

2. Set connection Redis parameters

3. Configure the Spring cache manager

Cache configuration class, which configures the cache manager, configures the global expiration time of the cache, serialization and other parameters.

import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.*;
import java.time.Duration;

/** * Redis configuration class */
@Configuration
public class RedisConfig {

    /** * Configure the cache manager *@param Factory Redis Thread-safe connection factory *@return The cache manager */
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory) {
        // Generate two default configurations. The cache can be customized using the Config object
        RedisCacheConfiguration cacheConfig1 = RedisCacheConfiguration.defaultCacheConfig()
                // Set the expiration time to 10 minutes
                .entryTtl(Duration.ofMinutes(10))
                // Set the cache prefix
                .prefixKeysWith("cache:user:")
                // Disable caching null values
                .disableCachingNullValues()
                // Set key serialization
                .serializeKeysWith(keyPair())
                // Set value serialization
                .serializeValuesWith(valuePair());
        RedisCacheConfiguration cacheConfig2 = RedisCacheConfiguration.defaultCacheConfig()
                // Set the expiration time to 30 seconds
                .entryTtl(Duration.ofSeconds(30))
                .prefixKeysWith("cache:user_info:")
                .disableCachingNullValues()
                .serializeKeysWith(keyPair())
                .serializeValuesWith(valuePair());
        // Return the Redis cache manager
        return RedisCacheManager.builder(factory)
                .withCacheConfiguration("user", cacheConfig1)
                .withCacheConfiguration("userInfo", cacheConfig2)
                .build();
    }

    /** * configure key serialization *@return StringRedisSerializer* /
    private RedisSerializationContext.SerializationPair<String> keyPair() {
        return RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer());
    }

    / * * * configuration values serialization, use GenericJackson2JsonRedisSerializer replace the default serialization *@return GenericJackson2JsonRedisSerializer* /
    private RedisSerializationContext.SerializationPair<Object> valuePair() {
        return RedisSerializationContext.SerializationPair.fromSerializer(newGenericJackson2JsonRedisSerializer()); }}Copy the code

4. Define entity classes

User entity class

User

User information entity class

UserInfo

Define the service interface

UserService

import mydlq.club.example.entity.User;

/** * User business interface */
public interface UserService {

    /** * add account **@param The user account * /
    void addUser(User user);

    /** * get account **@param Username username *@return User information */
    User getUserByUsername(String username);

    /** * change the account **@param User User information *@return User information */
    User updateUser(User user);

    /** * Delete account *@param Username username */
    void deleteByUsername(String username);

}
Copy the code

UserInfoService

import mydlq.club.example.entity.UserInfo;

/** * User information service interface */
public interface UserInfoService {

    /** * Add user information **@param UserInfo User information */
    void addUserInfo(UserInfo userInfo);

    /** * Get user information **@param The name name *@return User information */
    UserInfo getByName(String name);

    /** * Modify user information **@param UserInfo User information *@return User information */
    UserInfo updateUserInfo(UserInfo userInfo);

    /** * Delete user information *@param The name name * /
    void deleteByName(String name);

}
Copy the code

6. Implement the service class

Implement methods in UserService and UserInfoService interfaces, which use @cacheable, @cacheput, and @cacheevict annotations to cache users and user information.

UserServiceImpl (User Business Implementation Class)

Note that for the sake of the demonstration, there is no database connection, and a member variable, userMap, is created temporarily to simulate database storage.

import mydlq.club.example.entity.User;
import mydlq.club.example.service.UserService;
import org.springframework.beans.BeanUtils;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import java.util.HashMap;

@Service
@CacheConfig(cacheNames = "user")
public class UserServiceImpl implements UserService {

    private HashMap<String, User> userMap = new HashMap<>();

    @Override
    public void addUser(User user) {
        userMap.put(user.getUsername(), user);
    }

    @Override
    @Cacheable(key = "#username",unless = "#result==null ")
    public User getUserByUsername(String username) {
        if(! userMap.containsKey(username)) {return null;
        }
        return userMap.get(username);
    }

    @Override
    @CachePut(key = "#user.username")
    public User updateUser(User user) {
        if(! userMap.containsKey(user.getUsername())){throw new RuntimeException("The user does not exist");
        }
        // Get the stored object
        User newUser = userMap.get(user.getUsername());
        // Copy the updated data to the new object, ignoring the user name information because it cannot be changed
        BeanUtils.copyProperties(user, newUser, "username");
        // Store the new object and update the old object information
        userMap.put(newUser.getUsername(), newUser);
        // Returns information about the new object
        return newUser;
    }

    @Override
    @CacheEvict(key = "#username")
    public void deleteByUsername(String username){ userMap.remove(username); }}Copy the code

UserInfoServiceImpl (User Information Business implementation)

Note that for the sake of the demonstration, there is no database connection, and a member variable, userInfoMap, is created temporarily to simulate database storage.

import mydlq.club.example.entity.UserInfo;
import mydlq.club.example.service.UserInfoService;
import org.springframework.beans.BeanUtils;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import java.util.HashMap;

@Service
@CacheConfig(cacheNames = "userInfo")
public class UserInfoServiceImpl implements UserInfoService {

    private HashMap<String, UserInfo> userInfoMap = new HashMap<>();

    @Override
    public void addUserInfo(UserInfo userInfo) {
        userInfoMap.put(userInfo.getName(), userInfo);
    }

    @Override
    @Cacheable(key = "#name", unless = "#result==null")
    public UserInfo getByName(String name) {
        if(! userInfoMap.containsKey(name)) {return null;
        }
        return userInfoMap.get(name);
    }

    @Override
    @CachePut(key = "#userInfo.name")
    public UserInfo updateUserInfo(UserInfo userInfo) {
        if(! userInfoMap.containsKey(userInfo.getName())) {throw new RuntimeException("The user information was not found");
        }
        // Get the stored object
        UserInfo newUserInfo = userInfoMap.get(userInfo.getName());
        // Copy the updated data to the new object, ignoring the user name information because it cannot be changed
        BeanUtils.copyProperties(userInfo, newUserInfo, "name");
        // Store the new object and update the old object information
        userInfoMap.put(newUserInfo.getName(), newUserInfo);
        // Returns information about the new object
        return newUserInfo;
    }

    @Override
    @CacheEvict(key = "#name")
    public void deleteByName(String name){ userInfoMap.remove(name); }}Copy the code

Create Controller

UserController

import mydlq.club.example.entity.User;
import mydlq.club.example.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

/**
 * 用户 Controller
 */
@RestController
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping("/user/{username}")
    public User getUser(@PathVariable String username) {
        return userService.getUserByUsername(username);
    }

    @PostMapping("/user")
    public String createUser(@RequestBody User user) {
        userService.addUser(user);
        return "SUCCESS";
    }

    @PutMapping("/user")
    public User updateUser(@RequestBody User user) {
        return userService.updateUser(user);
    }

    @DeleteMapping("/user/{username}")
    public String deleteUser(@PathVariable String username) {
        userService.deleteByUsername(username);
        return "SUCCESS"; }}Copy the code

UserInfoController

import mydlq.club.example.entity.UserInfo;
import mydlq.club.example.service.UserInfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

/** * User information Controller */
@RestController
public class UserInfoController {

    @Autowired
    private UserInfoService userInfoService;

    @GetMapping("/userInfo/{name}")
    public UserInfo getUserInfo(@PathVariable String name) {
        return userInfoService.getByName(name);
    }

    @PostMapping("/userInfo")
    public String createUserInfo(@RequestBody UserInfo userInfo) {
        userInfoService.addUserInfo(userInfo);
        return "SUCCESS";
    }

    @PutMapping("/userInfo")
    public UserInfo updateUserInfo(@RequestBody UserInfo userInfo) {
        return userInfoService.updateUserInfo(userInfo);
    }

    @DeleteMapping("/userInfo/{name}")
    public String deleteUserInfo(@PathVariable String name) {
        userInfoService.deleteByName(name);
        return "SUCCESS"; }}Copy the code

8. Start classes

Add the @enablecaching annotation to the startup class to EnableCaching.

conclusion

Here is the end of the article to share ha, have learned, learn the children’s shoes can enter the Redis interview actual combat oh!