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
classification | Related products | Typical application | data model | advantage | shortcoming |
---|---|---|---|---|---|
Key value | Tokyo, Cabinet/Tyrant,Redis,Voldemort,Berkeley DB | Content caching is mainly used to deal with the high access load of a large amount of data | A series of key value pairs | Quick query | The stored data is not structured |
Column storage database | Cassandra, HBase, Riak | Distributed file system | It is stored in column clusters to store the same column of data together | Fast search speed, strong scalability and easier distributed expansion | Relatively limited functions |
Document database | CouchDB, MongoDB | Web application (similar to key value, value is structured) | A series of key value pairs | The data structure requirements are not strict | The query performance is not high, and there is a lack of unified query syntax |
Graph database | Neo4J, InfoGrid, Infinite Graph | Social networks, recommendation systems, etc. Focus on building relationship maps | Figure structure | Using graph structure correlation algorithm | The 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
-
In POM XML import dependency
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
-
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
- cacheNames and key s must be consistent with those in @ Cacheable() before they can be updated correctly.
- @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
-
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(); } }
-
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"
-
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 structure | Structure reading and writing ability |
---|---|---|
String | It can be a string, integer or floating point number | Perform operations on the whole string or part of the string; Objects and floating-point numbers are incremented or decremented |
List | A linked list. Each node in the linked list contains a string | Push 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 |
Set | Unordered collection containing strings, and each string contained is unique and different | Add, 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 |
Hash | Unordered hash table containing key value pairs | Add, obtain and remove a single key value pair; Get all key value pairs |
Zset | An ordered mapping between a string member and a floating-point score. The order of elements is determined by the size of the score | Add, 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
- StringRedisTemplate inherits RedisTemplate.
- RedisTemplate is a generic class, while StringRedisTemplate is not.
- StringRedisTemplate can only operate on key value pairs with key=String and value=String. RedisTemplate can operate on any type of key value pairs.
- 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
- 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/...