Introduction and comparison of commonly used redis clients

Posted by pdaoust on Fri, 18 Feb 2022 19:48:47 +0100

Introduction and comparison of a common redis client

Jedis api online web address: http://tool.oschina.net/uploads/apidocs/redis/clients/jedis/Jedis.html

Redisson website address: https://redisson.org/

redisson git project address: https://github.com/redisson/redisson

Letuce website address: https://lettuce.io/

Letuce git project address: https://github.com/lettuce-io/lettuce-core

First, after spring boot2, support for redis connections defaulted to lettuce. This explains to some extent the advantages and disadvantages of lettuce and Jedis.

Concepts:

Jedis: Old brand Redis Of Java Implementing the client provides a more comprehensive Redis Command support,
Redisson: Distributed and scalable Java Data structure.
Lettuce: senior Redis Client, for thread-safe synchronization, asynchronous and responsive use, supports clustering, Sentinel,Pipelines and coders.

Advantage:

Jedis: More comprehensive offers Redis Operational characteristics
Redisson: Promote users to Redis Separation of concerns, providing many distributed related operations services, such as distributed locks, distributed collections, through Redis Supports Delayed Queues
Lettuce: Be based on Netty The event-driven communication layer of the framework whose method calls are asynchronous. Lettuce Of API Thread-safe, so you can operate on a single Lettuce Connect to complete various operations

Scalable:

Jedis: Use blocked I/O,And its method calls are synchronous, the program flow needs to wait sockets Finished processing I/O Can be executed, asynchronous is not supported. Jedis Client instances are not thread-safe, so they need to be used through connection pools Jedis. 
Redisson: Be based on Netty The event-driven communication layer of the framework whose method calls are asynchronous. Redisson Of API Thread-safe, so you can operate on a single Redisson Connect to complete various operations
Lettuce: Be based on Netty The event-driven communication layer of the framework whose method calls are asynchronous. Lettuce Of API Thread-safe, so you can operate on a single Lettuce Connect to complete various operations
lettuce Ability to support redis4,Need java8 And above.
lettuce Is based on netty Realized and redis Make synchronous and asynchronous communication.

Letuce vs. jedis:

jedis Make Direct Connection redis server,If it is non-thread safe in a multi-threaded environment, then only the connection pool is used for each jedis Instances add physical connections;
lettuce Connections are based on Netty Of the connection instance ( StatefulRedisConnection)It can be accessed concurrently across multiple threads. StatefulRedisConnection Thread-safe, so a connection instance can accommodate concurrent access in a multi-threaded environment, which is also scalable design. If a connection instance is not enough, you can add it as needed.
Redisson Distributed and scalable Java Data structure, and Jedis It is simpler, does not support string operations, does not support sorting, transactions, pipelines, partitions, etc. Redis Characteristic. Redisson The purpose is to promote user pairing Redis Separate concerns allow users to focus more on their business logic.

Summary:

priority of use Lettuce,If you need advanced distributed features such as distributed locks, distributed collections, etc., add Redisson Use in combination because Redisson Operations on strings are poorly supported by themselves.
In some high concurrent scenarios, such as second-hand killing, ticket-grabbing, and purchasing, there is competition for core resources, commodity inventory, poor control, inventory may be reduced to a negative number, oversold, or a unique increase ID,Because web Applications are deployed on multiple machines and simple synchronous locking is not possible. For high concurrency, 1000/s Concurrent, databases can change from row locks to table locks, which can cause severe performance degradation. Relatively, redis Distributed locks are a relatively good choice. redis Officially recommended Redisson Distributed locks and related services are provided. 

Some Java client access is listed on the official website, including Jedis/Redisson/Jredis/JDBC-Redis, where Jedis and Redisson are officially recommended. Common Jedis.

Two SpringBoot Integration Jedis

brief introduction

When we use springboot to build micro-services, we still need redis cache to cache some data and store some high-frequency accessed data in many cases. It is more difficult to use redis directly. Here, we use jedis to implement redis cache for efficient caching

Introducing Jedis dependencies

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
</dependency>

Because the jedis version is referenced by default in SpringBoot.

So we don't need to configure the version number of jedis to introduce the jedis dependency directly.

application.yml

For example, in application. The following information is configured in yml:

spring:
  redis:
    port: 6379
    password: guoweixin
    host: 192.168.20.135
    jedis:
      pool:
        max-idle: 6    #Maximum number of idles
        max-active: 10 #maximum connection
        min-idle: 2    #Minimum number of idles
    timeout: 2000   #connection timed out

Write Config

Create a class: com.qfjy.config.jedis.JedisConfig

package com.qfjy.config.jedis;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

/**
 * @ClassName JedisConfig
 * @Description TODO
 * @Author guoweixin
 * @Version 1.0
 */
@Configuration
public class JedisConfig {

    private Logger logger = LoggerFactory.getLogger(JedisConfig.class);

    @Value("${spring.redis.host}")
    private String host;

    @Value("${spring.redis.port}")
    private int port;

    @Value("${spring.redis.password}")
    private String password;
    @Value("${spring.redis.timeout}")
    private int timeout;
	
    @Value("${spring.redis.jedis.pool.max-active}")
    private int maxActive;

    @Value("${spring.redis.jedis.pool.max-idle}")
    private int maxIdle;

    @Value("${spring.redis.jedis.pool.min-idle}")
    private int minIdle;

    @Bean
    public JedisPool  jedisPool(){
        JedisPoolConfig jedisPoolConfig=new JedisPoolConfig();
        jedisPoolConfig.setMaxIdle(maxIdle);
        jedisPoolConfig.setMinIdle(minIdle);
        jedisPoolConfig.setMaxTotal(maxActive);

        JedisPool jedisPool=new JedisPool(jedisPoolConfig,host,port,timeout,password);

        logger.info("JedisPool Connection Successful:"+host+"\t"+port);

        return jedisPool;
    }
}

Test Configuration

@SpringBootTest
public class JedisApplicationTests {
	@Autowired
    private JedisPool jedisPool;

    @Test
 public   void contextLoads() {
        System.out.println(jedisPool);
        //Get a Jedis connection in the connection pool
        Jedis jedis=jedisPool.getResource();
        jedis.set("haha","Hello");
        jedis.set("name","guoweixin");
        //Close the current connection
        jedis.close();

    }

Encapsulation Tool Class

JedisUtil

package com.qfjy.config.jedis;
import org.springframework.beans.factory.annotation.Autowired;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
/**
 * @ClassName JedisUtils
 * @Description TODO
 * @Author guoweixin
 * @Version 1.0
 */
@Component
public class JedisUtils {
    @Autowired
    private JedisPool jedisPool;

    /**
     * Get Jedis Resources
     */
    public Jedis getJedis(){
        return jedisPool.getResource();
    }
    /**
     * Release Jedis Connection
     */
    public void close(Jedis jedis){
        if(jedis!=null){
            jedis.close();
        }
    }
    .......................

}

test

Jedis Operation String Type

Business JedisServiceImpl Class

/**
 * @ClassName JedisServiceImpl
 * @Description TODO
 * @Author guoweixin
 * @Version 1.0
 */
@Service
@Log  //Log Processing
public class JedisServiceImpl {

    @Autowired
    private JedisUtils jedisUtils;
    /**
     * Test String
     * Query value based on key
     */
    public String  getString(String key){
        Jedis jedis=jedisUtils.getJedis();
        String val=null;
        if(!jedis.exists(key)){
            val="QianFeng Nanjing";
            log.info(key+"stay MYSQL The results of a query in the database are:"+val);
            jedis.set(key,val);
            log.info(key+"Save in Redis Medium. Value is:"+val);

        }else{
             val=jedis.get(key);
            log.info(key+"Yes Redis Data queried in. Value is:"+val);
        }
        jedisUtils.close(jedis); //Release Resources
        return val;
    }
}    

unit testing

@SpringBootTest
public class JedisTests {
    @Autowired
    private JedisServiceImpl jedisService;

    @Test
    void t1(){
       String val= jedisService.getString("name");
       System.out.println(val);
    }
}

Jedis operation Hash type

Business JedisServiceImpl Class

@Service
@Log  
public class JedisServiceImpl {

    @Autowired
    private JedisUtils jedisUtils;
/**
     * Testing the jedis operation hash type
     * Query user information based on user ID
     * First determine if there is a Redis.
     * If it does not exist, query in the database. Coexist in Redis
     * If so, query Redis directly and return
     */
    public User selectBy(String id){
        String key="user:id"; //Generate key s for the same specification based on Rules
        User user=new User();;
        Jedis jedis=jedisUtils.getJedis();
        if(!jedis.exists(key)){
            //Query in database and store
            user.setId(id);
            user.setName("QianFeng Nanjing JAVA");
            user.setAge(20);
            log.info("The user information queried in the database is:"+user);
            Map<String,String> map=new  HashMap();
            map.put("id",user.getId());
            map.put("name",user.getName());
            jedis.hset(key,map);
            log.info(key+"Save Successfully Redis:"+user);
        }
        else{
            Map<String,String> map= jedis.hgetAll(key);
            user.setId(map.get("id"));
            user.setName(map.get("name"));
            log.info(key+"Redis Queried out in:"+map);
        }
        jedisUtils.close(jedis);

        return user;
    }
}

unit testing

@SpringBootTest
public class JedisTests {
    @Autowired
    private JedisServiceImpl jedisService;
    @Test
    void hash(){
       User user= jedisService.selectBy("1001");
        System.out.println(user);
    }
}

Three SpringBoot2. redis in X (lettuce)

Jedis -"is further encapsulated. --" RedisTemplate

JDBCTemplate

RestTemplate

java Code Operation Redis,Need to use Jedis,that is redis Support java Third Party Class Library
 Be careful:Jedis2.7 Cluster operations are supported in versions above

maven configuration

New SpringBoot2.0.3 WEB Project, in MAVEN pom. Add the following dependencies to the XML file

<dependencies>
       <!--Default is lettuce Client-->
       <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <!-- redis rely on commons-pool This dependency must be added -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>
		<!-- Test Library Dependency -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
  </dependencies>

hibernate–>JPA–>SpringData

Profile Configuration

application.yml

spring:
  redis:
    port: 6379
    password: guoweixin
    host: 192.168.20.135
    lettuce:
      pool:
        max-active: 8 # Maximum number of connections in connection pool (use a negative value to indicate no limit)
        max-idle: 8 # Maximum idle connection in connection pool
        min-idle: 0 # Minimum idle connection in connection pool
        max-wait: 1000 # Maximum blocking wait time for connection pool (use a negative value to indicate no limit)
      shutdown-timeout: 100   # Close timeout

redis configuration class

JdbcTemplate ->JDBC is further encapsulated.

RedisTemplate ->redis is further encapsulated (lettuce)

brief introduction

Write cache configuration class RedisConfig to tune cache default configuration, RedisTemplate < String, Object > is more type compatible

You can see that in the redisTemplate() method, Jackson JsonRedisSerializer replaces Redis's default serialization: JdkSerializationRedisSerializer

There are several serialized classes in spring-data-redis:

GenericToStringSerializer: Any object can be generalized to character creation and serialization
Jackson 2JsonRedisSerializer: Serialize Object object to json character creation (same as Jackson JsonRedisSerializer)
JdkSerializationRedisSerializer: Serialize java objects
String RedisSerializer: Simple string serialization

JdkSerializationRedisSerializer Serialization Serializer Serialized object must implement Serializable interface. Serialized object has other content besides attribute content. It is long and difficult to read. By default, this serialization method is used

The contents of the storage are as follows:

"\xac\xed\x00\x05sr\x00!com.oreilly.springdata.redis.User\xb1\x1c \n\xcd\xed%\xd8\x02\x00\x02I\x00\x03ageL\x00\buserNamet\x00\x12Ljava/lang/String;xp\x00\x00\x00\x14t\x00\x05user1"

Jackson Json RedisSerializer serialization, serialized object does not need to implement Serializable interface, serialized result is clear, easy to read, less bytes stored, fast

The contents of the storage are as follows:

"{"userName":"guoweixin","age":20}"

String RedisSerializer Serialization

In general, if the key and value are string strings, you can just use this

RedisConfig class

package com.qfjy.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.interceptor.KeyGenerator;
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.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.lang.reflect.Method;

/**
 * @ClassName RedisConfig
 * @Description TODO
 * @Author guoweixin
 * @Version 1.0
 */
@Configuration
public class RedisConfig extends CachingConfigurerSupport {

    /**
     * Customize the cache key generation policy. The default build strategy is to make configuration injections that you don't understand (scrambled content) through Spring's Dependency Injection feature and that this class is a configuration class that can be more customized
     *
     * @return
     */
    @Bean
    @Override
    public KeyGenerator keyGenerator() {
        return new KeyGenerator() {
            @Override
            public Object generate(Object target, Method method, Object... params) {
                StringBuilder sb = new StringBuilder();
                sb.append(target.getClass().getName());
                sb.append(method.getName());
                for (Object obj : params) {
                    sb.append(obj.toString());
                }
                return sb.toString();
            }
        };
    }

    /**
     * Cache Configuration Manager
     */
    @Bean
    public CacheManager cacheManager(LettuceConnectionFactory factory) {
        //Create RedisCacheWriter object as lock write
        RedisCacheWriter writer = RedisCacheWriter.lockingRedisCacheWriter(factory);
        //Create default cache configuration object
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
        RedisCacheManager cacheManager = new RedisCacheManager(writer, config);
        return cacheManager;
    }

    @Bean
    public RedisTemplate<String,Object> redisTemplate(LettuceConnectionFactory factory){
        RedisTemplate<String,Object> template = new RedisTemplate <>();
        template.setConnectionFactory(factory);

        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);

        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();

        // Configure how hashKey and hashValue are serialized when RedisTemplate is returned using the annotation @Bean.
        // key uses String serialization
        template.setKeySerializer(stringRedisSerializer);
        // value serialization using jackson
        template.setValueSerializer(jackson2JsonRedisSerializer);

        // hash's key also uses String's serialization
        template.setHashKeySerializer(stringRedisSerializer);
        // hash's value serialization uses jackson
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }
}

Code Samples

RedisServiceImpl

Test String Type

@Service
@Log
public class RedisServiceImpl {
    @Autowired
    private RedisTemplate<String,Object> redisTemplate;


    /**
     * Plain Cache
     * @param key key
     * @return true Successful false failed
     */
    public String getString(String key) {
        if(redisTemplate.hasKey(key)) {
            log.info("Redis Query in");
           return (String) redisTemplate.opsForValue().get(key);
        }else{
            String val="guoweixin";
            redisTemplate.opsForValue().set(key, val);
            log.info("Queried in the database");
            return  val;
        }

    }

    /**
     * Normal Cache Placement
     * @param key key
     * @param value value
     * @param expireTime Timeout (seconds)
     * @return true Successful false failed
     */
    public Boolean set(String key, Object value, int expireTime) {
        try {
            redisTemplate.opsForValue().set(key, value, expireTime, TimeUnit.SECONDS);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
}    

Test Hash Type

@Service
@Log
public class RedisServiceImpl {
    @Autowired
    private RedisTemplate<String,Object> redisTemplate;

    @Resource(name="redisTemplate")
    private HashOperations<String,String,User> hash;
     /**
     * Determine if the key exists, and if it exists, query in Redis
     * If it does not exist, query in MYSQL and get the result, adding it to Redis Hash
     * @param id
     * @return
     */
      public User selectUserById1(String id){
        if(hash.hasKey("user",id)){
            log.info("Redis Query Objects in");
            return  hash.get("user",id);
        }else{
            User u=new User();
            u.setId(id);
            u.setName("guoweixin");
            u.setAge(22);
            log.info("mysql Query Objects in");
            hash.put("user",id,u);
            return u;
        }
    }
}   
    

hash Type Code Example

package com.qfjy.redis.demo.service.impl;

import com.qfjy.redis.demo.service.HashCacheService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

@Service("hashCacheService")
public class HashCacheServiceImpl implements HashCacheService {
    private final static Logger log = LoggerFactory.getLogger(HashCacheServiceImpl.class);

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * Get a value in MAP
     * @param key  key
     * @param item term
     * @return value
     */
    public Object hget(String key, String item) {
        return redisTemplate.opsForHash().get(key, item);
    }

    /**
     * Get all keys corresponding to hashKey
     * @param key key
     * @return Corresponding multiple key values
     */
    public Map <Object, Object> hmget(String key) {
        return redisTemplate.opsForHash().entries(key);
    }

    /**
     * Add key-value pairs as map sets
     * @param key key
     * @param map Corresponds to multiple key values
     * @return true Successful false failed
     */
    public boolean hmset(String key, Map <String, Object> map) {
        try {
            redisTemplate.opsForHash().putAll(key, map);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * HashSet And set the time
     * @param key  key
     * @param map  Corresponds to multiple key values
     * @param time Time (seconds)
     * @return true Successful false failed
     */
    public boolean hmset(String key, Map <String, Object> map, long time) {
        try {
            redisTemplate.opsForHash().putAll(key, map);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * Put data into a hash table and create it if it doesn't exist
     * @param key   key
     * @param item  term
     * @param value value
     * @return true Successful false failed
     */
    public boolean hset(String key, String item, Object value) {
        try {
            redisTemplate.opsForHash().put(key, item, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * Put data into a hash table and create it if it doesn't exist
     * @param key   key
     * @param item  term
     * @param value value
     * @param time  Time (seconds) Note: If the existing hash table has time, this will replace the original time
     * @return true Successful false failed
     */
    public boolean hset(String key, String item, Object value, long time) {
        try {
            redisTemplate.opsForHash().put(key, item, value);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * Delete values from hash table
     * @param key  Key cannot be null
     * @param item Item can make multiple cannot be null
     */
    public void hdel(String key, Object... item) {
        redisTemplate.opsForHash().delete(key, item);
    }

    /**
     * Determine if there is a value for this item in the hash table
     * @param key  Key cannot be null
     * @param item Item cannot be null
     * @return true Existence false does not exist
     */
    public boolean hHasKey(String key, String item) {
        return redisTemplate.opsForHash().hasKey(key, item);
    }

    /**
     * hash Increment, if it does not exist, creates one and returns the added value
     * @param key  key
     * @param item term
     * @param by   To add a few (greater than 0)
     * @return
     */
    public long hincr(String key, String item, long by) {
        return redisTemplate.opsForHash().increment(key, item, by);
    }

    /**
     * hash Decreasing
     * @param key  key
     * @param item term
     * @param by   To reduce memory (less than 0)
     * @return
     */
    public long hdecr(String key, String item, long by) {
        return redisTemplate.opsForHash().increment(key, item, -by);
    }

    /**
     * Gets the hashMap value in the specified variable.
     * @param key
     * @return Return LIST Object
     */
    @Override
    public List<Object> values(String key) {
        return redisTemplate.opsForHash().values(key);
    }

    /**
     *  Gets the key in the variable.
     * @param key
     * @return Return SET Collection
     */
    @Override
    public Set<Object> keys(String key) {
        return  redisTemplate.opsForHash().keys(key);
    }

    /**
     * Gets the length of the variable.
     * @param key key
     * @return  Return Length
     */
    @Override
    public long size(String key) {
        return redisTemplate.opsForHash().size(key);
    }

    /**
     * Gets the value in a variable as a collection.
     * @param key
     * @param list
     * @return Returns the LIST set value
     */
    @Override
    public List multiGet(String key, List list) {
        return redisTemplate.opsForHash().multiGet(key,list);
    }

    /**
     *  If the value of a variable exists, you can add key-value pairs that do not exist to the variable
     *  If the variable does not exist, add a new variable and add key-value pairs to it.
     * @param key
     * @param hashKey
     * @param value
     */
    @Override
    public void putIfAbsent(String key, String hashKey, Object value) {
        redisTemplate.opsForHash().putIfAbsent(key,hashKey,value);
    }

    /**
     * Match to get key-value pairs, ScanOptions.NONE To get all the key pairs, ScanOptions.scanOptions().match("map1").build()
     * Matching obtains the key-value pairs of the key bit map1 and cannot be mismatched.
     * @param key
     * @param options
     * @return
     */
    @Override
    public Cursor<Map.Entry<Object, Object>> scan(String key, ScanOptions options) {
        return  redisTemplate.opsForHash().scan(key,options);
    }

    /**
     * Deleting key-value pairs from a variable can pass in multiple parameters and delete multiple key-value pairs.
     * @param key key
     * @param hashKeys MAP KEY in
     */
    @Override
    public void delete(String key, String... hashKeys) {
        redisTemplate.opsForHash().delete(key,hashKeys);
    }

    public boolean expire(String key, long seconds) {
        return redisTemplate.expire(key,seconds,TimeUnit.SECONDS);
    }

    /**
     * delete
     * @param keys
     */
    @Override
    public void del(String... keys) {
        if (keys != null && keys.length > 0) {
            if (keys.length == 1) {
                redisTemplate.delete(keys[0]);
            } else {
                redisTemplate.delete(CollectionUtils.arrayToList(keys));
            }
        }
    }

    @Override
    public long getExpire(String key) {
        return 0;
    }
}

Topics: Database MySQL Redis