Redis series (III) integration of redis transactions and spring boot

Time:2021-9-19

It is more or less used in NoSQL development, and it is also a necessary knowledge point for interview. I’ve asked every interview in recent days. However, I feel that the answer is not good, and there are still many knowledge points that need to be sorted out. Here, I’ll comb through several redis notes, and then add the above test questions.

Redis series:

  1. Redis series (I) introduction to redis
  2. Redis series (II) 8 data types of redis
  3. Redis series (III) integration of redis transactions and spring boot
  4. Redis series (IV) redis profile and persistence
  5. Redis series (V) publish subscribe mode, master-slave replication and sentinel mode
  6. Redis series (VI) redis’s cache penetration, cache breakdown and cache avalanche
  7. Redis series (VII) redis interview questions
  8. Redis Command Reference

1. Business

Essence of redis transaction: a collection of commands! All commands in a transaction will be serialized and executed in sequence during the execution of the transaction.

Execute a set of commands at one time, sequentially and exclusively.

Redis transactions do not have the concept of isolation level.

All commands are not executed directly in a transaction. They are executed only when the execution command is initiated (exec).

Redis ensures atomicity for a single command, but transactions do not guarantee atomicity.

Redis transaction command:

  • Open transaction: multi
  • Order to join the team
  • Execute transaction: Exec
  • Undo transaction: discard

1. Normal execution of transactions

127.0.0.1:6379> multi 		#  Open transaction
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> exec 			#  Execute transaction
1) OK
2) OK
3) "v2"
4) OK
127.0.0.1:6379>

2. Abandon transaction

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set m1 n1
QUEUED
127.0.0.1:6379> set m2 n2
QUEUED
127.0.0.1:6379> DISCARD 		#  Abandon transaction
OK
127.0.0.1:6379> get m1 		#  None of the commands in the transaction queue will be executed
(nil)

Compiled exception: if the command has an error, all commands in the transaction will not be executed

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> setget k3 v3 		#  Wrong command
(error) ERR unknown command `setget`, with args beginning with: `k3`, `v3`, 
127.0.0.1:6379> set k4 v4
QUEUED
127.0.0.1:6379> exec 		#  Error in executing transaction
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get k4 		#  All commands will not be executed
(nil)
127.0.0.1:6379>

Runtime exception: if an error is reported in the execution result of a command in the transaction, other commands can be executed normally, and the error command throws an exception

127.0.0.1:6379> set k1 "v1"
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incr k1 		#  Will fail
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> get k3
QUEUED
127.0.0.1:6379> exec
1) (error) ERR value is not an integer or out of range 		#  The first command failed to execute, and the rest were executed normally
2) OK
3) OK
4) "v3"
127.0.0.1:6379> get k2
"v2"
127.0.0.1:6379>

3. Monitor watch (frequently asked in interview)

Pessimistic lock: very pessimistic. I think there will be problems whenever and lock everything. Affect efficiency, the actual situation will generally use optimistic lock.

Optimistic lock: very optimistic. I think there will be no problem at any time, so I don’t lock it. When updating data, judge whether the monitored data has been modified during this period, that is, obtain version.

First, understand the role of watch in redis transactions. The watch command can monitor one or more keys. Once one of the keys is modified (or deleted), subsequent transactions will not be executed. The monitoring continues until the exec command (the commands in the transaction are executed after exec, so the key value of watch monitoring can be modified after the multi command). Suppose we monitor multiple keys before the transaction is executed through the watch command. If the value of any key changes after the watch command, the transaction executed by the exec command will be abandoned and a null multi bulk response will be returned to notify the caller of the transaction execution failure.

Therefore, it should be noted that after the watch monitoring key, operate these keys, otherwise the watch may not work.

Redis monitoring test

Normal test:

127.0.0.1:6379> set money 100		
OK
127.0.0.1:6379> set out 0
OK
127.0.0.1:6379> watch money 		#  Monitor money objects
OK
127.0.0.1:6379> multi 		#  When the transaction ends normally and money does not change during execution, the execution will be successful
OK
127.0.0.1:6379> DECRBY money 20
QUEUED
127.0.0.1:6379> INCRBY out 20
QUEUED
127.0.0.1:6379> exec
1) (integer) 80
2) (integer) 20
127.0.0.1:6379>

Test multiple threads to modify the value, and use watch as redis’s optimistic lock operation.

127.0.0.1:6379> set money 100
OK
127.0.0.1:6379> set out 10
OK
127.0.0.1:6379> watch money 	#  Monitor money
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> DECRBY money 10
QUEUED
127.0.0.1:6379> DECRBY out 10
QUEUED
127.0.0.1:6379> exec 		#  Before execution, modify the value of money in another thread B. the following is the execution failure.
(nil)
127.0.0.1:6379>

B thread:

[[email protected] bin]# redis-cli -p 6379
127.0.0.1:6379> set money 30
OK

If the modification fails, just get the latest value.

127.0.0.1:6379> UNWATCH 		#  Transaction execution failed, unlock first
OK
127.0.0.1:6379> WATCH money 		#  Get the latest value and monitor again. It is equivalent to select version in MySQL
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> DECRBY money 1
QUEUED
127.0.0.1:6379> INCRBY out 1
QUEUED
127.0.0.1:6379> exec 		#  During execution, the monitored values will be compared. If there is a change, the execution will fail.
1) (integer) 29
2) (integer) 11
127.0.0.1:6379>

2、Jedis

Use java to operate redis. Jedis is a Java link development tool officially recommended by redis and a middleware for Java to operate redis.

1. Import dependency

redis.clients
        jedis
        3.2.0
    

    
    
    
        com.alibaba
        fastjson
        1.2.68

2. Test: start the local Windows version of redis

package cn.itzhouq;

import redis.clients.jedis.Jedis;

public class TestPing {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("localhost", 6379, 10);
        System.out.println(jedis.ping()); // PONG
    }
}

affair

package cn.itzhouq;

import com.alibaba.fastjson.JSONObject;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;

/**
 *Test transaction
 */
public class TestTX {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("localhost", 6379);
        jedis.flushDB();
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("hello", "world");
        jsonObject.put("name", "xxx");

        //Open transaction
        Transaction multi = jedis.multi();
        String result = jsonObject.toJSONString();

        try {
            multi.set("user1", result);
            multi.set("user2", result);
            int i = 1 / 0; //  Simulation anomaly
            multi.exec(); //  Execute transaction
        } catch (Exception e) {
            multi.discard(); //  Abandon transaction
            e.printStackTrace();
        } finally {
            System.out.println(jedis.get("user1")); //  During normal execution, {"name": "XXX", "hello": "world"} // null
            System.out.println(jedis.get("user1"));
            jedis.close(); //  Close link
        }
    }
}

3. Spring boot integration

Spring data is also a project as famous as spring boot.

Note: after spring boot 2. X, the original jedis is replaced by lettuce.

Jedis: direct connection and multi-threaded operation are not safe. If you want to avoid insecurity, use jedis pool, which is more like bio mode.

Lettuce: with netty, instances can be shared among multiple threads. There is no thread insecurity. Thread links can be reduced, more like NiO mode.

#All configuration classes of spring boot have an automatic configuration class redistemplate
#Automatic configuration classes will bind a properties configuration file. RedisProperties

Read the source code:

1588351399447

@Configuration
@ConditionalOnClass({RedisOperations.class})
@EnableConfigurationProperties({RedisProperties.class})
@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
public class RedisAutoConfiguration {
    public RedisAutoConfiguration() {
    }

    @Bean
    @ConditionalOnMissingBean(
        name = {"redisTemplate"}
    )// we can define a redistemplate to replace the default one.
    public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
        //The default redistemplate does not have too many settings, and redis objects need to be serialized.
        //The two generics are both object types. We need to forcibly replace them with 
        RedisTemplate template = new RedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

    @Bean
    @Conditionalonmissingbean // since string type is the most commonly used in redis, a bean is proposed separately
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }
}

Test:

1. Import dependency:

org.springframework.boot
    spring-boot-starter-data-redis

2. Configure connection

#Configure redis
spring.redis.host=127.0.0.1
spring.redis.port=6379

3. Testing

package cn.itzhouq;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
public class Redis02SpringbootApplicationTests {

    @Autowired
    private RedisTemplate redisTemplate;

    @Test
    public void contextLoads() {
        //In addition to basic operations, our common methods can be operated directly through redistemplate, such as transaction and crud
        redisTemplate.opsForValue().set("name", "xiaoming");
        System.out.println(redisTemplate.opsForValue().get("name")); // xiaoming
    }
}

Take a look at the source code: redistemplate.class

//Serialization configuration
@Nullable
private RedisSerializer keySerializer = null;
@Nullable
private RedisSerializer valueSerializer = null;
@Nullable
private RedisSerializer hashKeySerializer = null;
@Nullable
private RedisSerializer hashValueSerializer = null;
private RedisSerializer stringSerializer = RedisSerializer.string();
public void afterPropertiesSet() {
    super.afterPropertiesSet();
    boolean defaultUsed = false;
    if (this.defaultSerializer == null) {
        //JDK serialization is used by default, which will make the string escape
        this.defaultSerializer = new JdkSerializationRedisSerializer(this.classLoader != null ? this.classLoader : this.getClass().getClassLoader());
    }

    // ...
}

We use JSON serialization, so we need to customize the configuration class

1. Serialization

Write an entity class user to test serialization.

package cn.itzhouq.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.stereotype.Component;

@Component
@NoArgsConstructor
@AllArgsConstructor
@Data
public class User {
    private String name;
    private int age;
}

Test serialization:

@Test
public void test() throws JsonProcessingException {
    User user = new User("xiaoming", 3);
    redisTemplate.opsForValue().set("user", user);
    System.out.println(redisTemplate.opsForValue().get("user"));
}

Throw exception:

Caused by: java.lang.IllegalArgumentException: DefaultSerializer requires a Serializable payload but received an object of type [cn.itzhouq.pojo.User]
	at org.springframework.core.serializer.DefaultSerializer.serialize(DefaultSerializer.java:43)
	at org.springframework.core.serializer.support.SerializingConverter.convert(SerializingConverter.java:63)
	... 35 more

DefaultSerializer requires a SerializableThe default serialization requires the entity class to implement the serialization interface. So modify user:

public class User implements Serializable {
    private String name;
    private int age;
}

result:

User(name=xiaoming, age=3)

The results are normal, but the console is still escaped.

127.0.0.1:6379> keys *
1) "\xac\xed\x00\x05t\x00\x04user"
127.0.0.1:6379>

Serialization using Jackson:

@Test
public void test() throws JsonProcessingException {
    //In general, JSON is used to transfer objects in development
    User user = new User("xiaoming", 3);
    String jsonUser = new ObjectMapper().writeValueAsString(user);
    redisTemplate.opsForValue().set("user", jsonUser);
    System.out.println(redisTemplate.opsForValue().get("user")); // {"name":"xiaoming","age":3}
}

Whether the user implements the serializable interface or not, the console results are displayed normally, but the view in the client is still escaped.

If you don’t want to use JDK serialization, you can write redistemplate yourself.

2. Customize redistemplate

package cn.itzhouq.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
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.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 *Write your own redistemplate
 */
@Configuration
public class RedisConfig {

    @Bean
    @SuppressWarnings("all")
    public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        //For the convenience of development, it is generally used 
        RedisTemplate template = new RedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);

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

        //Serialization of string
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        //The key is serialized by string
        template.setKeySerializer(stringRedisSerializer);
        //The key of hash is also serialized by string
        template.setHashKeySerializer(stringRedisSerializer);
        //Value is serialized by Jackson
        template.setValueSerializer(jackson2JsonRedisSerializer);
        //The value serialization method of hash is Jackson
        template.setHashKeySerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();

        return template;
    }
}

Injection and testing:

@Autowired
@Qualifier("redisTemplate")
private RedisTemplate redisTemplate;

@Test
public void test() throws JsonProcessingException {
    //In general, JSON is used to transfer objects in development
    User user = new User("xiaoming", 3);
    String jsonUser = new ObjectMapper().writeValueAsString(user);
    redisTemplate.opsForValue().set("user", jsonUser);
    System.out.println(redisTemplate.opsForValue().get("user")); // {"name":"xiaoming","age":3}
}

View in client:

127.0.0.1:6379> keys *
1) "user"
127.0.0.1:6379>

At this time, the object is not escaped.

The next note will introduce redis’s configuration file and persistence.

Recommended Today

A detailed explanation of the differences between Perl and strawberry Perl and ActivePerl

Perl is the abbreviation of practical extraction and report language “practical report extraction language”. Application of activestateperl and strawberry PERL on Windows platformcompiler。 Perl   The relationship between the latter two is that C language and Linux system have their own GCC. The biggest difference between activestate Perl and strawberry Perl is that strawberry Perl […]