JAVA缓存-Redis入门级使用

前言

Java缓存实现方案有很多,最基本的自己使用Map去构建缓存,再高级点的使用Ehcache或者Goolgeguava作为内存缓存框架,Ehcache可以满足单机缓存的需求(Ehcache的具体使用在我过往的文章中有所介绍),如果我们是多台机子共用缓存数据的话,Ehcache可通过rmijgroupjms的方式实现,但是实用性与操作性不高且复杂,现时大部分应用仅用Ehcache作为单机缓存使用,这时候我们可以通过搭建缓存服务器解决多机使用的问题,常见的缓存服务器有MemcachedRedis等。

现时业界主流大多使用Redis。所以本文主要介绍在Java中如何使用Redis。至于如何搭建Redis,我在过往的文章中已有所介绍,不知道如何搭建的同学,可以参考我过往的文章,下文所用到相关的Redis信息均为搭建教程中的信息。

PS:文章中所用到的示例代码,部分参考至开源项目iBase4J,特此声明。

Java连接Redis

Java连接Redis官方推荐的是使用JedisRedisson进行连接操作,SpringRedis有很好的支持,所以此文我结合Spring中的Spring DataRedis进行操作。

1. maven引用


    org.springframework.data
    spring-data-redis
    1.8.7.RELEASE


    redis.clients
    jedis
    2.9.0


    org.redisson
    redisson
    3.5.5

2. 建立Redis配置文件

classpath下建立Redis配置文件redis.properties

如果同学们是搭建Redis高可用架构,是通过向外提供VIP虚拟IP的方式连接Redis,则只需在配置文件中将redis.host=172.16.2.185单机IP改为VIP虚拟IPredis.host=172.16.2.250即可实现Redis高可用,而不需要使用Spring提供的RedisSentinel方案实现Redis高可用。

#VIP
#redis.host=172.16.2.250
redis.host=172.16.2.185
redis.port=6379
redis.password=123456
#最小空闲数
redis.minIdle=2
#最大空闲数
redis.maxIdle=10
#最大连接数
redis.maxTotal=1000
#最大建立连接等待时间
redis.maxWaitMillis=3000
#客户端超时时间单位是毫秒
redis.timeout=120000
#明是否在从池中取出连接前进行检验,如果检验失败,则从池中去除连接并尝试取出另一个  
redis.testOnBorrow=true
redis.expiration=600

3. 与Spring结合

3.1 配置Jedis实现

classpath下建立文件夹spring用于存放所有与spring相关的配置文件。在spring文件夹下建立spring-redis.xml,主要用于注入Jedis



    
    
    
    
          
        
          
        
          
        
          
        
          
          
    
        
    
    
      
      
      
      
      
  
    
    
    
    
    
    
    
        
        
        
        
        
        
    
    
    
        
    
    
    
        
        
        
    

3.2 配置Redisson实现

spring文件夹下建立spring-redisson.xml,主要用于注入Redisson



    
    
    
    
        
        
        
        
        
        
        
    
    
    
    
        
    

4. 代码实现

4.1 定义获取缓存的工具类CacheUtil

这个类主要是用于获取缓存管理器,因为Jedis封装Redis基本操作的接口比较友好,所以基本操作使用Jedis实现,但是将Redis当做分布式锁使用时,如果是自行用Jedis中的setNX + 时间戳过程方法实现的话,会略显复杂,还可能写的不严谨,存在原子性操作或者死锁等问题。此处的分布式锁实现使用Redisson帮我们封装好的方法实现加锁与解锁,顺便提一句,Redisson加锁操作是使用lua脚本一次执行加锁与设置过期的操作的,所以不存在原子性问题。这处暂时不展开讨论分布式锁的问题,日后有空再和大家一同探讨分布式锁的问题。

package com.easylink.mall.core.cache.redis;

import com.easylink.mall.core.support.util.PropertiesFileUtil;

public class CacheUtil {
    /**
     * 缓存管理器,主要执行缓存操作
     */
    private static CacheManager cacheManager;
    /**
     * 锁管理器,主要执行加锁与解锁操作
     */
    private static CacheManager lockManager;

    public static void setCacheManager(CacheManager cacheManager) {
        CacheUtil.cacheManager = cacheManager;
    }

    public static void setLockManager(CacheManager cacheManager) {
        CacheUtil.lockManager = cacheManager;
    }

    public static CacheManager getCache() {
        return cacheManager;
    }

    public static CacheManager getLockManager() {
        return lockManager;
    }

    /** 获取锁 */
    public static boolean tryLock(String key) {
        int expires = 1000 * PropertiesFileUtil.getInstance("redis.properties").getInt("redis.lock.expires", 180);
        return lockManager.setnx(key, expires);
    }

    /** 获取锁 */
    public static boolean getLock(String key) {
        return lockManager.lock(key);
    }

    /** 解锁 */
    public static void unlock(String key) {
        lockManager.unlock(key);
    }
}
4.2 定义缓存操作管理接口CacheManager
package com.easylink.mall.core.cache.redis;

import java.io.Serializable;
import java.util.Set;

/**
 * 缓存操作管理接口
 * 
 * @author Ben.
 *
 */
public interface CacheManager {
    /**
     * 根据key获取对象
     * 
     * @param key
     * @return
     */
    Object get(final String key);

    /**
     * 根据正则表达式获取对象
     * 
     * @param pattern
     *            正则表达式
     * @return
     */
    Set getAll(final String pattern);

    /**
     * 设置key-value
     * 
     * @param key
     * @param value
     * @param seconds
     *            过期时间(秒)
     */
    void set(final String key, final Serializable value, int seconds);

    /**
     * 设置key-value 过期时间使用默认配置值
     * 
     * @param key
     * @param value
     */
    void set(final String key, final Serializable value);

    /**
     * 根据key判断某一对象是否存在
     * 
     * @param key
     * @return 是否存在
     */
    Boolean exists(final String key);

    /**
     * 根据key删除对象
     * 
     * @param key
     */
    void del(final String key);

    /**
     * 根据正则表达式删除对象
     * 
     * @param pattern
     *            正则表达式
     * @return
     */
    void delAll(final String pattern);

    /**
     * 根据key获取对应对象的类型
     * 
     * @param key
     * @return 对应对象的类型
     */
    String type(final String key);

    /**
     * 设置key的过期时间
     * 
     * @param key
     * @param seconds
     * @return 是否设置成功
     */
    Boolean expire(final String key, final int seconds);

    /**
     * 设置key在指定时间点后过期
     * 
     * @param key
     * @param unixTime
     * @return 是否成功
     */
    Boolean expireAt(final String key, final long unixTime);

    /**
     * 获取对应key的过期时间
     * 
     * @param key
     * @return
     */
    Long ttl(final String key);

    /**
     * 设置新值并返回旧值
     * 
     * @param key
     * @param value
     * @return 旧值
     */
    Object getSet(final String key, final Serializable value);

    /**
     * 对key进行加锁
     * 
     * @param key
     * @return 是否加锁成功
     */
    boolean lock(String key);

    /**
     * 对key进行解锁
     * 
     * @param key
     */
    void unlock(String key);

    /**
     * 根据key设置对应哈希表对象的field - value
     * 
     * @param key
     * @param field
     * @param value
     */
    void hset(String key, Serializable field, Serializable value);

    /**
     * 根据key获取对应哈希表的对应field的对象
     * 
     * @param key
     * @param field
     * @return
     */
    Object hget(String key, Serializable field);

    /**
     * 根据key删除对应哈希表的对应field的对象
     * 
     * @param key
     * @param field
     * @return
     */
    void hdel(String key, Serializable field);

    /**
     * 指定的 key 不存在时,为 key 设置指定的value
     * 
     * @param key
     * @param value
     * @return 是否设置成功
     */
    boolean setnx(String key, Serializable value);

    /**
     * 对应key的值自增
     * 
     * @param key
     * @return 自增后的值
     */
    Long incr(String key);

    /**
     * 用指定的字符串覆盖给定 key 所储存的字符串值,覆盖的位置从偏移量 offset 开始
     * 
     * @param key
     * @param offset
     *            偏移量
     * @param value
     */
    void setrange(String key, long offset, String value);

    /**
     * 用于获取存储在指定 key 中字符串的子字符串。字符串的截取范围由 start 和 end 两个偏移量决定(包括 start 和 end 在内)。
     * 
     * @param key
     * @param startOffset
     * @param endOffset
     * @return
     */
    String getrange(String key, long startOffset, long endOffset);

    /**
     * 将value设置至指定key的set集合中
     * 
     * @param key
     * @param value
     */
    void sadd(String key, Serializable value);

    /**
     * 获取指定key的set集合
     * 
     * @param key
     * @return
     */
    Set sall(String key);

    /**
     * 删除指定key的set集合中的value
     * 
     * @param key
     * @param value
     * @return
     */
    boolean sdel(String key, Serializable value);
}

 
 
4.3 定义RedisHelper

基于SpringRedisTemplate实现CacheManager接口,主要用于对缓存的基本操作,不用于分布式锁作用,此处的分布式锁实现不严谨,不当做参考


/**
 * Redis缓存辅助类
 * 
 * @author ShenHuaJie
 * @version 2016年4月2日 下午4:17:22
 */
public final class RedisHelper implements CacheManager {
    private static final Logger logger = Logger.getLogger(RedisHelper.class);
    private RedisSerializer keySerializer;
    private RedisSerializer valueSerializer;
    private RedisTemplate redisTemplate;
    private final Integer EXPIRE = PropertiesFileUtil.getInstance("redis.properties").getInt("redis.expiration");

    @SuppressWarnings("unchecked")
    public void setRedisTemplate(RedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
        this.keySerializer = (RedisSerializer) redisTemplate.getKeySerializer();
        this.valueSerializer = (RedisSerializer) redisTemplate.getValueSerializer();
        CacheUtil.setCacheManager(this);
    }

    @Override
    public final Object get(final String key) {
        // 先过期
        expire(key, EXPIRE);
        // 后取值
        return redisTemplate.boundValueOps(key).get();
    }

    @Override
    public final Set getAll(final String pattern) {
        Set values = new HashSet();
        Set keys = redisTemplate.keys(pattern);
        for (Serializable key : keys) {
            expire(key.toString(), EXPIRE);
            values.add(redisTemplate.opsForValue().get(key));
        }
        return values;
    }

    @Override
    public final void set(final String key, final Serializable value, int seconds) {
        redisTemplate.boundValueOps(key).set(value);
        expire(key, seconds);
    }

    @Override
    public final void set(final String key, final Serializable value) {
        redisTemplate.boundValueOps(key).set(value);
        expire(key, EXPIRE);
    }

    @Override
    public final Boolean exists(final String key) {
        return redisTemplate.hasKey(key);
    }

    @Override
    public final void del(final String key) {
        redisTemplate.delete(key);
    }

    @Override
    public final void delAll(final String pattern) {
        redisTemplate.delete(redisTemplate.keys(pattern));
    }

    @Override
    public final String type(final String key) {
        expire(key, EXPIRE);
        return redisTemplate.type(key).getClass().getName();
    }

    @Override
    public final Boolean expire(final String key, final int seconds) {
        return redisTemplate.expire(key, seconds, TimeUnit.SECONDS);
    }


    @Override
    public final Boolean expireAt(final String key, final long unixTime) {
        return redisTemplate.expireAt(key, new Date(unixTime));
    }

    @Override
    public final Long ttl(final String key) {
        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
    }

    @Override
    public final void setrange(final String key, final long offset, final String value) {
        redisTemplate.boundValueOps(key).set(value, offset);
        expire(key, EXPIRE);
    }

    @Override
    public final String getrange(final String key, final long startOffset, final long endOffset) {
        expire(key, EXPIRE);
        return redisTemplate.boundValueOps(key).get(startOffset, endOffset);
    }

    @Override
    public final Object getSet(final String key, final Serializable value) {
        expire(key, EXPIRE);
        return redisTemplate.boundValueOps(key).getAndSet(value);
    }

    @Override
    public boolean setnx(String key, Serializable value) {
        RedisConnectionFactory factory = redisTemplate.getConnectionFactory();
        RedisConnection redisConnection = null;
        try {
            redisConnection = RedisConnectionUtils.getConnection(factory);
            if (redisConnection == null) {
                return redisTemplate.boundValueOps(key).setIfAbsent(value);
            }
            logger.info(keySerializer);
            logger.info(valueSerializer);
            return redisConnection.setNX(keySerializer.serialize(key), valueSerializer.serialize(value));
        } finally {
            RedisConnectionUtils.releaseConnection(redisConnection, factory);
        }
    }

    @Override
    public boolean lock(String key) {
        RedisConnectionFactory factory = redisTemplate.getConnectionFactory();
        RedisConnection redisConnection = null;
        try {
            redisConnection = RedisConnectionUtils.getConnection(factory);
            if (redisConnection == null) {
                return redisTemplate.boundValueOps(key).setIfAbsent("0");
            }
            return redisConnection.setNX(keySerializer.serialize(key), valueSerializer.serialize("0"));
        } finally {
            RedisConnectionUtils.releaseConnection(redisConnection, factory);
        }
    }

    @Override
    public void unlock(String key) {
        redisTemplate.delete(key);
    }

    @Override
    public void hset(String key, Serializable field, Serializable value) {
        redisTemplate.boundHashOps(key).put(field, value);
    }

    @Override
    public Object hget(String key, Serializable field) {
        return redisTemplate.boundHashOps(key).get(field);
    }

    @Override
    public void hdel(String key, Serializable field) {
        redisTemplate.boundHashOps(key).delete(field);
    }

    @Override
    public void sadd(String key, Serializable value) {
        redisTemplate.boundSetOps(key).add(value);
    }

    @Override
    public Set sall(String key) {
        return redisTemplate.boundSetOps(key).members();
    }

    @Override
    public boolean sdel(String key, Serializable value) {
        return redisTemplate.boundSetOps(key).remove(value) == 1;
    }

    @Override
    public Long incr(String key) {
        return redisTemplate.boundValueOps(key).increment(1L);
    }

}
 
 
4.4 定义RedissonHelper

基于Redisson实现CacheManager接口,主要用于实现基于Redis的分布式锁

package com.easylink.mall.core.cache.redisson;

import java.io.Serializable;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import org.redisson.api.RBucket;
import org.redisson.api.RType;
import org.redisson.api.RedissonClient;

import com.easylink.mall.core.cache.redis.CacheManager;
import com.easylink.mall.core.cache.redis.CacheUtil;
import com.easylink.mall.core.support.util.PropertiesFileUtil;

/**
 * 
 * Redis缓存辅助类
 * 
 */
public class RedissonHelper implements CacheManager {
    private RedissonClient redissonClient;
    private final Integer EXPIRE = PropertiesFileUtil.getInstance("redis.properties").getInt("redis.expiration");

    public void setRedissonClient(Client client) {
        this.redissonClient = client.getRedissonClient();
        CacheUtil.setLockManager(this);
    }

    private RBucket getRedisBucket(String key) {
        return redissonClient.getBucket(key);
    }

    @Override
    public final Object get(final String key) {
        RBucket temp = getRedisBucket(key);
        expire(temp, EXPIRE);
        return temp.get();
    }

    @Override
    public final void set(final String key, final Serializable value) {
        RBucket temp = getRedisBucket(key);
        temp.set(value);
        expire(temp, EXPIRE);
    }

    @Override
    public final void set(final String key, final Serializable value, int seconds) {
        RBucket temp = getRedisBucket(key);
        temp.set(value);
        expire(temp, seconds);
    }

    public final void multiSet(final Map temps) {
        redissonClient.getBuckets().set(temps);
    }

    @Override
    public final Boolean exists(final String key) {
        RBucket temp = getRedisBucket(key);
        return temp.isExists();
    }

    @Override
    public final void del(final String key) {
        redissonClient.getKeys().delete(key);
    }

    @Override
    public final void delAll(final String pattern) {
        redissonClient.getKeys().deleteByPattern(pattern);
    }

    @Override
    public final String type(final String key) {
        RType type = redissonClient.getKeys().getType(key);
        if (type == null) {
            return null;
        }
        return type.getClass().getName();
    }

    /**
     * 在某段时间后失效
     * 
     * @return
     */
    private final void expire(final RBucket bucket, final int seconds) {
        bucket.expire(seconds, TimeUnit.SECONDS);
    }

    /**
     * 在某个时间点失效
     * 
     * @param key
     * @param unixTime
     * @return
     * 
     */
    @Override
    public final Boolean expireAt(final String key, final long unixTime) {
        return redissonClient.getBucket(key).expireAt(new Date(unixTime));
    }

    @Override
    public final Long ttl(final String key) {
        RBucket rBucket = getRedisBucket(key);
        return rBucket.remainTimeToLive();
    }

    @Override
    public final Object getSet(final String key, final Serializable value) {
        RBucket rBucket = getRedisBucket(key);
        return rBucket.getAndSet(value);
    }

    @Override
    public Set getAll(String pattern) {
        Set set = new HashSet();
        Iterable keys = redissonClient.getKeys().getKeysByPattern(pattern);
        for (Iterator iterator = keys.iterator(); iterator.hasNext();) {
            String key = iterator.next();
            set.add(getRedisBucket(key).get());
        }
        return set;
    }

    @Override
    public Boolean expire(String key, int seconds) {
        RBucket bucket = getRedisBucket(key);
        expire(bucket, seconds);
        return true;
    }

    @Override
    public void hset(String key, Serializable field, Serializable value) {
        redissonClient.getMap(key).put(field, value);
    }

    @Override
    public Object hget(String key, Serializable field) {
        return redissonClient.getMap(key).get(field);
    }

    @Override
    public void hdel(String key, Serializable field) {
        redissonClient.getMap(key).remove(field);
    }

    public void sadd(String key, Serializable value) {
        redissonClient.getSet(key).add(value);
    }

    public Set sall(String key) {
        return redissonClient.getSet(key).readAll();
    }

    public boolean sdel(String key, Serializable value) {
        return redissonClient.getSet(key).remove(value);
    }

    @Override
    public boolean lock(String key) {
        return redissonClient.getLock(key).tryLock();
    }

    @Override
    public void unlock(String key) {
        redissonClient.getLock(key).unlock();
    }

    @Override
    public boolean setnx(String key, Serializable value) {
        try {
            return redissonClient.getLock(key).tryLock(Long.valueOf(value.toString()), TimeUnit.MILLISECONDS);
        } catch (InterruptedException e) {
            return false;
        }
    }

    @Override
    public Long incr(String key) {
        return null;
    }

    @Override
    public void setrange(String key, long offset, String value) {
    }

    @Override
    public String getrange(String key, long startOffset, long endOffset) {
        return null;
    }
}
 
 
4.5 定义Redisson客户端配置实现类
package com.easylink.mall.core.cache.redisson;

import java.util.HashSet;
import java.util.Set;

import org.apache.commons.lang3.StringUtils;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.ClusterServersConfig;
import org.redisson.config.Config;
import org.redisson.config.MasterSlaveServersConfig;
import org.redisson.config.SingleServerConfig;

/**
 * Redis连接配置
 * 
 * @author ShenHuaJie
 * @since 2017年8月23日 上午9:36:53
 */
public class Client {
    /**
     * Redis server address
     *
     */
    private String address;

    /**
     * Password for Redis authentication. Should be null if not needed
     */
    private String password;

    /**
     * Redis cluster node urls list
     */
    private Set nodeAddresses = new HashSet();

    /**
     * Redis master server address
     */
    private String masterAddress;

    /**
     * Redis slave servers addresses
     */
    private Set slaveAddresses = new HashSet();

    private RedissonClient redissonClient;

    public void init() {
        Config config = new Config();
        if (StringUtils.isNotBlank(address)) {
            SingleServerConfig serverConfig = config.useSingleServer().setAddress(address);
            if (StringUtils.isNotBlank(password)) {
                serverConfig.setPassword(password);
            }
        } else if (!nodeAddresses.isEmpty()) {
            ClusterServersConfig serverConfig = config.useClusterServers()
                    .addNodeAddress(nodeAddresses.toArray(new String[] {}));
            if (StringUtils.isNotBlank(password)) {
                serverConfig.setPassword(password);
            }
        } else if (masterAddress != null && !slaveAddresses.isEmpty()) {
            MasterSlaveServersConfig serverConfig = config.useMasterSlaveServers().setMasterAddress(masterAddress)
                    .addSlaveAddress(slaveAddresses.toArray(new String[] {}));
            if (StringUtils.isNotBlank(password)) {
                serverConfig.setPassword(password);
            }
        }
        this.redissonClient = Redisson.create(config);
    }

    public RedissonClient getRedissonClient() {
        return redissonClient;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public void setNodeAddresses(String nodeAddresse) {
        if (nodeAddresse != null) {
            String[] nodeAddresses = nodeAddresse.split(",");
            for (int i = 0; i < nodeAddresses.length; i++) {
                if (StringUtils.isNotEmpty(nodeAddresses[i])) {
                    this.nodeAddresses.add(nodeAddresses[i]);
                }
            }
        }
    }

    public void setMasterAddress(String masterAddress) {
        this.masterAddress = masterAddress;
    }

    public void setSlaveAddresses(String slaveAddresse) {
        if (slaveAddresse != null) {
            String[] slaveAddresses = slaveAddresse.split(",");
            for (int i = 0; i < slaveAddresses.length; i++) {
                if (StringUtils.isNotEmpty(slaveAddresses[i])) {
                    this.slaveAddresses.add(slaveAddresses[i]);
                }
            }
        }
    }
}

4.6 定义properties配置文件读取工具类
package com.easylink.mall.core.support.util;

import java.util.HashMap;

import org.apache.commons.configuration2.PropertiesConfiguration;
import org.apache.commons.configuration2.builder.fluent.Configurations;
import org.apache.commons.configuration2.ex.ConfigurationException;
import org.apache.log4j.Logger;

/**
 * 资源文件读取工具
 * 
 * @author Ben.
 *
 */
public class PropertiesFileUtil {

    private static Logger logger = Logger.getLogger(PropertiesFileUtil.class);
    // 当打开多个资源文件时,缓存资源文件
    private static HashMap configMap = new HashMap();
    // 默认资源文件名称
    private static final String NAME = "config.properties";

    // 私有构造方法,创建单例
    private PropertiesFileUtil() {
    }

    public static synchronized PropertiesConfiguration getInstance() {
        return getInstance(NAME);
    }

    public static synchronized PropertiesConfiguration getInstance(String name) {
        PropertiesConfiguration propertiesConfiguration = configMap.get(name);
        if (propertiesConfiguration == null) {
            Configurations configs = new Configurations();
            try {
                propertiesConfiguration = configs.properties(name);
            } catch (ConfigurationException e) {
                logger.error("can not load properties file,name : " + name);
            }
            configMap.put(name, propertiesConfiguration);
        }
        return propertiesConfiguration;
    }
}

5. 测试

编写单元测试用例,测试是否搭建成功

5.1 测试基本操作
package com.easylink.mall.core.test;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.easylink.mall.core.cache.redis.CacheUtil;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring/spring-redis.xml")
public class JedisTest {

     @Test
    public void execute() {
        CacheUtil.getCache().set("test-jedis", "test-value");
        Object testValue = CacheUtil.getCache().get("test-jedis");
        System.out.println(String.valueOf(testValue));
    }

}

5.2 测试分布式锁
package com.easylink.mall.core.test;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.easylink.mall.core.cache.redis.CacheUtil;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring/spring-redisson.xml")
public class RedissonTest {

    @Test
    public void execute() {
        // 加锁
        CacheUtil.getLockManager().lock("test-jedis");
        // 解锁
        CacheUtil.getLockManager().unlock("test-jedis");
    }

}

总结

至此,上述过程已经说明了如何在JAVA中使用Redis进行一些缓存的基本操作或者是当作分布式锁去使用。内容比较简单,基础。但是适用于初学者去学习,毕竟先学会入门的使用,然后再对其的某些功能或者特性去深入研究,这样能让自己更好的去学习一种技术。楼主由于太久没有更新文章,所以先写一篇简单的找一下感觉。迟点再和大家一同探究Redis的一些知识和常见问题,如:基础的数据结构,缓存和数据库一致性问题,缓存雪崩问题,缓存击穿问题等。谢谢大家的支持,如果此文对你有所帮助,请点个赞,谢谢。

你可能感兴趣的:(JAVA缓存-Redis入门级使用)