Cool breeze AAA 2022-02-13 07:43:34 阅读数:905
1.redis Introductory cases : introduce jar package Writing test classes
2. Second kill business logic — Distributed locking mechanism
3.SpringBoot Integrate Redis
Compile configuration class optimization jedis Object creation
Cache application scenario analysis
Object and the JSON Interturn –ObjectMapper----> Encapsulate as a tool api
Implementation of commodity classification cache ( Trees – Select category )
Create a package in the test class , class .
Be careful : The package of the test class should also be under the package of the main startup class or its sub package .
<!--spring Integrate redis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
</dependency>
manage In test class
package com.jt.test;
import org.junit.jupiter.api.Test;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;
import redis.clients.jedis.params.SetParams;
//@SpringBootTest If you need to introduce... In the test class spring Only the container mechanism uses this annotation , Such as attribute injection .
public class TestRedis {
/** * 1. Test remote redis Is the server available * Ideas : * 1. Instantiation jedis Tools API Link to (host:port) * 2. Use objects to execute redis command The method is to order * Error reporting and debugging : * 1. Check Redis.conf Whether the configuration file of is modified as required ip/ Protect / backstage * 2.redis Starting mode redis-server redis.conf * 3. Turn off firewall systemctl stop firewalld.service * */
@Test
public void test01(){
String host="192.168.126.129";//redis Where ip Address
int port =6379;//redis The port of
Jedis jedis =new Jedis(host,port);// Note that this leads to redis.clients package
jedis.set("cgb2006"," study hard ");
System.out.println(jedis.get("cgb2006"));
}
/** * 2. demand : * 1. towards redis Insert data k-v * 2. by key Set the timeout 60 Seconds after the failure * 3. Threads sleep 3 second * 4. obtain key The remaining life of * * Problem description : The following code exists bug, Is the data bound to be deleted ??? If there is an exception in the middle , The following data will not execute . * Problem specification : If you use redis And you need to add a timeout In general, the atomic requirements should be met . * Atomic row : Or at the same time , Or fail at the same time . Be careful : Must be completed at the same time . */
@Test
public void test02() throws InterruptedException {
/* Before optimization : Jedis jedis=new Jedis("192.168.126.129",6379); jedis.set(" Baokemeng ", " Little fire dragon "); int a=1/0;// Throw an exception . jedis.expire(" Baokemeng ",60);// Set up key Effective time of Thread.sleep(3000);//1 second =1000 millisecond System.out.println(jedis.ttl(" Baokemeng "));// Check key The rest of the time */
/** Optimized writing : * If you want to add a timeout to the data ,redis It also provides optimized api Method . * */
Jedis jedis=new Jedis("192.168.126.129",6379);
jedis.setex(" Baokemeng ",60," Little fire dragon ");
System.out.println(jedis.get(" Baokemeng "));
}
/** * 3. demand : * If you find that key Do not modify data when it already exists , If key Data is modified when it doesn't exist . * problem : Look at the code like this if els Too many levels ,redis The optimized writing method is provided , Instead of if-else. */
@Test
public void test03(){
Jedis jedis=new Jedis("192.168.126.129",6379);
/* if(jedis.exists("redis")){ System.out.println("key Already exists , No modification "); }else{ jedis.set("redis", "aaaa"); }*/
// explain : If redisa If it exists, it will not be modified , Take the original value , If redisa No value exists " test nx Methods "
jedis.setnx("redisa", " test nx Methods ");
System.out.println(jedis.get("redisa"));
}
/** * 4. demand : * 1. When the user is asked to assign a value , If the data exists, no assignment is made .setnx * 2. It is required that in the assignment operation , You have to set the timeout time And it requires atomicity settex * problem : this 2 Two methods cannot be used at the same time , Then at the same time meet this 2 Need a new way to learn set Overload method of */
@Test
public void test04(){
Jedis jedis=new Jedis("192.168.126.129",6379);
SetParams setParams=new SetParams();
//nx:key There is no assignment ex: second xx: Yes key Only when the value is assigned px: millisecond
setParams.nx().ex(10);// Lock operation
jedis.set("bbbb", " Implement business operations ",setParams );
System.out.println(jedis.get("bbbb"));
jedis.del("bbbb"); // Unlock operation
}
@Test
public void testList05() throws InterruptedException{
Jedis jedis=new Jedis("192.168.126.129",6379);
jedis.lpush("list", "1","2","3");
System.out.println(jedis.rpop("list"));
}
@Test
public void testTx06() throws InterruptedException{
Jedis jedis=new Jedis("192.168.126.129",6379);
//1. Open transaction
Transaction transaction=jedis.multi();
try {
transaction.set("aa","aa");
//2. Commit transaction
transaction.exec();
} catch (Exception e) {
e.printStackTrace();
//3/ Roll back the transaction
transaction.discard();
}
}
}
The original price :6988 —> Present price 1 block
Problem description :1 Mobile phone 20 Everyone shows that the rush purchase is successful And paid for 1 Yuan …
Problem specification :
1.tomcat There are multiple servers
2. Database data only 1 Share ( There is only one copy of master-slave database data )
3. High concurrency is inevitable .( Multiple people rush to buy )
How to realize the rush purchase ???
analysis :a Buy a cell phone , To query the database , Database stock minus 1,b At this time, you can also access the database, but the database has not been reduced. At this time, there is a mobile phone in the database , This is going back to accessing the database minus 1.
explain :tomact Is a multithreaded operation , Multithreading preempting the same resource will inevitably lead to thread safety problems .
explain : Synchronous lock can only solve tomcat Internal problems , Can't solve multiple tomcat Concurrency issues .
analysis : Although in tomact A synchronous lock is added inside , however tomact There are multiple servers , There will still be thread safety issues .
Ideas :
1. Locks should use third-party operations , Locks should be public .
2. principle : If the lock is being used , Other users can't operate .
3. Strategy : The user to redis Save a key, If redis There is key Someone is using the lock , Other users are not allowed to operate . If redis There is no key , I can use the lock .
4. risk : How to solve the deadlock problem . Set the timeout .
Interview answer what is distributed lock ???
Distributed locks are generally available to everyone in a third party , Commonly used for distributed locks Redis Realization , towards Redis Add data to ,key It's a lock ,value Is the password .tomact The server accesses redis, If key If it exists, it cannot be executed ,key Can only be executed if it does not exist , And the key-value Put in redis in . Some deadlocks may occur when locking , So add timeout when locking , But the unlocking code is usually placed in finally in ,finally Anyone can use the code in , So to avoid the lock being released in advance by others, you need to add some password verification , The lock can only be removed if the passwords are consistent . Finally, the lock of my house can only be unlocked by me , Even if you don't understand it for a period of time, it will be released .
problem : Will there be simultaneous locking ??? Can't , because redis It's a single thread operation .
explain :
1). In the introductory case, each use redis Need to be new One Jedis object , More trouble . So it's best to give the rights of common objects to spring Containers to manage , Use wherever you need it @Autwried Note injection is enough .
2). because redis Then it will be used by other servers , So the best way is to Redis Save the configuration class to common in .
explain : Because this configuration is public , So put it in conmon In the configuration directory under the project .
package com.jt.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import redis.clients.jedis.Jedis;
@Configuration // I am a configuration class
@PropertySource("classpath:/properties/redis.properties") // Write a living form , Import profile
public class JedisConfig {
// Inside key No repetition , If properties and yml They all have the same key with yml Subject to
@Value("${redis.host}") // This is a spel expression (springel) and el yes jsp The expression of
private String host;
@Value("${redis.port}")
private Integer port;
/** * take jedis Object to spring Container management */
@Bean
public Jedis jedis(){
// Because writing code is not conducive to expansion , So add the fixed configuration to the configuration file
return new Jedis(host,port);
}
}
explain :
1. There is no need to update in real time, but it consumes a lot of data in the database .
2. Need to be updated in real time , But data that is not updated frequently .
3. At a certain time, the data that is accessed heavily and updated frequently . But the cache used for this kind of data cannot be the same as ordinary cache , This cache must be guaranteed not to be lost , Otherwise there will be big problems .
summary : Caching is suitable for small changes in data , But the business of often querying the database .
1. Paging query : After adding data, the database records will be changed , On the paging page, the overall order will change , Data with large changes like this Not suitable for Do the cache .
2. Leaf category is suitable for caching : Every time the page is refreshed, no matter whether the page changes or not, a query will be carried out in the background .
3. Select the category suitable for caching : Just click the parent directory to access the database for query .
problem : Why transform ???
reason : Now the data queried in the tree directory is stored in List< EasyUITree> In this collection object , and redis Most of the types of storage required in are String type , Therefore, the queried data cannot be directly stored in redis In cache .
solve : Pass the object through Api The method in is transformed into json Save string into redis in . because Redis Essentially, String character string , When taking the value, it passes API Turn it into an object and take it out .
API:
JSON Native offers :ObjectMapper
Ali provides :Fastjson
reflection :
1). Use it directly List< EasyUITree>.toString()
Convert to string redis Can't you do it in the middle school ???
answer : no way , Although it can be converted into a string , But when there is no way to get the value, restore the string to an object .
2). use @ResponseBody This annotation converts the object into json String line ???
answer : no way , This annotation is equivalent to telling spring MVC The return value of the method is converted to JSON, Now it's time to use the data in the business layer approach , So this method is not suitable for . So I can only learn one set API Implement objects and JSON The transformation of data .
manage In test class
package com.jt.test;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.jt.pojo.ItemDesc;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
public class TestObjectMapper {
// Simple objects and JSON Interturn :
@Test
public void test01(){
// Define a tool API object
ObjectMapper objectMapper = new ObjectMapper();
// Create item table object test
ItemDesc itemDesc = new ItemDesc();
itemDesc.setItemId(100L).setItemDesc("json test ")
.setCreated(new Date()).setUpdated(new Date());
try {
/** * 1. Convert objects to json, Because the assigned value may not be standardized , Not all values can be converted into json There are risks , * So you need to handle exceptions , Catch or throw . */
String result = objectMapper.writeValueAsString(itemDesc);
System.out.println(result);
/** * 2. take json Data into objects , Strings and objects cannot be converted directly , Only through the reflection mechanism .. * Reflection : Given xxx.class Instantiate the object after the type . Make use of the object's get/set Method to assign a value to a property . * The first parameter is the data to be converted The second parameter is the object type to be converted . */
ItemDesc itemDesc2 = objectMapper.readValue(result,ItemDesc.class);
System.out.println(itemDesc2.getCreated());// Output parent's attribute creation time
System.out.println(itemDesc2.getItemDesc());// Output your own ItemDesc attribute
/* Output object , But the result is only 2 individual , We actually assigned a value with 4 Why is this data like this ??? Because this method we use is @Data Annotation generated , This annotation has a feature , Display only Their own properties do not display the properties of the parent , In fact, there are still some, but they don't show .*/
System.out.println(itemDesc2.toString());
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
// Collection objects and JSON Interturn :
@Test
public void test02(){
ObjectMapper objectMapper = new ObjectMapper();
ItemDesc itemDesc = new ItemDesc();
itemDesc.setItemId(100L).setItemDesc("json test 1")
.setCreated(new Date()).setUpdated(new Date());
ItemDesc itemDesc2 = new ItemDesc();
itemDesc2.setItemId(100L).setItemDesc("json test 2")
.setCreated(new Date()).setUpdated(new Date());
List<ItemDesc> list = new ArrayList<>();
list.add(itemDesc);
list.add(itemDesc2);
try {
//1. Convert objects to JSON ( In the same way )
String json = objectMapper.writeValueAsString(list);
System.out.println(json);
//2. take json Convert to object Transformed json strand The type of transformation :list Collection gets the type as an object .
List<ItemDesc> list2 = objectMapper.readValue(json, list.getClass());
System.out.println(list2);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
}
explain : In fact, you need to use this in your business Api When converting , Exceptions often need to be handled by yourself (try–catch) Instead of throwing . But in the code try–catch Too much will lead to structural confusion , So writing tools Api Simplify .
step :
Method 1: Convert any object into JSON.
Method 2: Will be arbitrary JSON String is converted to an object .
Request to complete exception handling .
package com.jt.util;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.util.StringUtils;
public class ObjectMapperUtil {
/** * explain : It's too much trouble to create an object every time you use it , A constant object can be defined only once . * advantage 1: Object only saves space * advantage 2: Objects are not allowed to be tampered with by others */
private static final ObjectMapper MAPPER = new ObjectMapper();
/** * 1. Convert any object to JSON * reflection 1: Any object should use Object Object to pick up * reflection 2: The return value is JSON strand So it should be String * reflection 3: How to convert JSON FASTJSON( Ali's )/objectMapper( Native ) */
public static String toJSON(Object object){
// Define static convenient calls
try {
if(object == null){
// Judge whether the data transmitted by the user is empty
throw new RuntimeException(" Parameters passed object by null, Please check carefully ");
}
return MAPPER.writeValueAsString(object);// No exceptions, direct conversion
} catch (JsonProcessingException e) {
e.printStackTrace();
// There was an exception in the conversion process , What to do with ? You should check for exceptions , Convert to runtime exception .
throw new RuntimeException(" The object passed does not support json conversion / Check if there is get/set Method ");
}
}
/** * 2. Will be arbitrary JSON String is converted to an object * demand : What type the user passes , What kind of object do I return * <T> T Customized a generic object , Consistency ensures that what type is passed is what type is defined , The return value is the defined generic , * This approach is generally applicable to tools API And the compilation of the source code . * */
public static <T> T toObject(String json,Class<T> target){
// String manipulation tool API
if(StringUtils.isEmpty(json) || target == null){
throw new RuntimeException(" The parameter passed cannot be null null");
}
try {
return MAPPER.readValue(json,target);
} catch (JsonProcessingException e) {
e.printStackTrace();
throw new RuntimeException("json Abnormal transformation ");
}
}
}
1. Definition Redis Medium key, key It must be the only one that cannot repeat . save take There should be consistency .
parendid Is the only thing that can be used as key, In order to see famous people, I intend to parentid The pre splicing keyword is used as the prefix , Generally, the prefix and characters are separated by ::
Connect .
The final effect is :key = “ITEM_CAT_PARENTID::70”
2. according to key Go to redis Query in , With or without data
3. No data Then query the database for records , Then save the data to redis In order to facilitate subsequent use .
4. There's data Indicates that the user is not querying for the first time You can return the cached data directly .
/** * Business needs : Realize the display of commodity classification , Through ajax request , Dynamically obtain the data of tree structure . * url Address : http://localhost:8091/item/cat/list * Parameters : parentId = 0 Query the first level commodity classification menu . * Return value result : List<EasyUITree> ( Because the outermost layer of the required tree parameter format is []) * matters needing attention : * 1. No information is passed when the tree structure is initialized . Pass only when child nodes are expanded Id, If it doesn't pass id, And initialization show the of first-class goods id, The parent of the presentation id Namely 0. * 2. What kind of data does the page deliver , What kind of data the backend must receive */
@RequestMapping("/list")
public List<EasyUITree> findItemCatList(Long id){
// This place first tests and queries the first level menu , So first put id Write dead .
// Use the three wood operator to judge . No transmission id Or pass it on id The situation of .
Long parentId = (id==null?0L:id);// Initialization not transmitted id, According to the father id=0 You can query the first level menu information .
//return itemCatService.findItemCatList(parentId);
return itemCatService.findItemCache(parentId);
}
/** * Instead, query the commodity classification from the cache ( Trees ), The original method still works . * @param parentId * @return */
@Autowired(required = false)// Ensure the normal execution of subsequent operations You can lazy load ( When using, create objects )
private Jedis jedis; // Guide pack :redis.clients.jedis.Jedis;
@Override
public List<EasyUITree> findItemCache(Long parentId) {
//0. Define a common return value object
List<EasyUITree> treeList = new ArrayList<>();
//1. Definition key
String key = "ITEM_CAT_PARENTID::"+parentId;
Long startTime = System.currentTimeMillis();// Record the start time
//2. retrieval redis The server , Does it contain key
if(jedis.exists(key)){
//3. Data exists Get cache data directly , And then it turns into an object
String json = jedis.get(key);// according to key obtain value
long endTime = System.currentTimeMillis();// End time
treeList = ObjectMapperUtil.toObject(json,treeList.getClass());//json Transfer object
System.out.println(" from redis Get data in , Time consuming :"+(endTime-startTime)+" millisecond ");
}else{
//4. The data doesn't exist You should query the database first , Then save the data to the cache .
treeList = findItemCatList(parentId);// Directly call the above method of querying from the database ( Original method )
long endTime = System.currentTimeMillis();// End time
//4.1 Save data to cache
String json = ObjectMapperUtil.toJSON(treeList);// Object turn json
jedis.setex(key, 7*24*60*60, json);// Store in cache and set timeout
System.out.println(" Query the database , Time consuming :"+(endTime-startTime)+" millisecond ");
}
return treeList;
}
test : Empty first redis cache :flushall
problem 1: Query cache is generally faster than query database 20 About times , Why is the first query so different ???
answer : For the first time, you need to connect to the database and establish communication , Communication takes time . After the first communication, a long connection relationship will be established ( Short time links will not close ), So you don't have to save time when establishing a connection again .
problem 2:
But the above write cache method is not good , Destroy the original code structure , Write business code and add a lot of cached code , Poor maintenance, etc … High coupling , solve — use AOP.
copyright:author[Cool breeze AAA],Please bring the original link to reprint, thank you. https://en.javamana.com/2022/02/202202130743270528.html