redis入门第十课:实战之缓存

1.为什么需要缓存?

我们知道redis缓存是存储key-value的内存数据库,内存访问数据更快捷。而在web项目中,对于读多写少的高并发场景,我们会经常使用缓存来进行优化。redis缓存的优点:
1:读写性能极高 , redis读对的速度是11万次/s , 写的速度是8.1万次/s.
2:redis 支持多种数据类型,redis存储的是 key–value格式的数据,其中key是字符串,但是value就有多种数据类型了:String , list ,map , set ,有序集合(sorted set)

2.redis缓存架构

项目中redis的架构如下所示:


redis入门第十课:实战之缓存_第1张图片
1556439840(1).png

3.redis缓存遇到的问题

3.1 缓存和数据库写入一致性问题

当写入数据时,会遇到一个问题:是先将数据更新到缓存,还是将数据更新到数据库。必须要知道的是,更新缓存和更新数据库不会是原子性的,如果在更新缓存成功后,未更新数据库,会导致数据异常。如果更新数据库,但是未更新缓存,在从获取数据时,数据也是异常的。这两种操作都会导致数据不一致的问题。所以,我们得根据系统的需求来评估,是先更新缓存还是更新数据库。
由于我们的系统读大于写,在这种场景下,只需要以数据库为主,先写数据库,再写缓存就好了。

3.2 缓存击穿问题

指查询一个不存在的key,而这个key扛着大并发,这时由于缓存没有就需要到数据库中查找,则此时数据库将面临大量的请求压力。

  • 将查询不到的key添加一个标志空值的value,比如:我们场景为查询一个标的的信息,如果这个标的信息不存在,我们设置一个空字符串返回。代码如下:
//无论数据是否为空,都推一个key到redis,防止数据为null时不走缓存
        List jsonList = new ArrayList();
        jsonList.add("");
  • 把所有存在的key都存到另外一个存储的Set集合里,查询的时候将不符合规则的key进行过滤。
    还有最常见的则是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。
    基本原理及要点:对于原理来说很简单,位数组+k个独立hash函数。将hash函数对应的值的位数组置1,查找时如果发现所有hash函数对应位都是1说明存在,很明显这个过程并不保证查找的结果是100%正确的。
  • 这些Key可能不是永远不存在,所以需要根据业务场景来设置过期时间。

3.3 缓存雪崩问题

缓存雪崩,是指在某一个时间段,缓存集中过期失效。这时客户端获取key时,无法获取到,只能从数据库中获取,那么短暂的时间内,大量请求都积压到了数据库,造成数据库雪崩。

  • 这个没有完美解决办法,但可以分析用户行为,尽量让失效时间点均匀分布
  • 批量缓存的对象是一个结果集,条目有10万条,缓存时间基础为 60602(sec),现在需要同时进行缓存。我的做法是默认生成一个随机数,如random(范围 0 - 1000),过期时间则设置为( 60602 + random ) 。
  • 做二级缓存,或者双缓存策略。

4. 缓存搭建

我们的web项目中采用的是哨兵。一主两从三哨兵。


redis入门第十课:实战之缓存_第2张图片
图片.png

5. 实际运用

Jedis是Redis的Java实现的客户端,其API提供了比较全面的Redis命令的支持。Redisson实现了分布式和可扩展的Java数据结构,和Jedis相比,功能较为简单,不支持字符串操作,不支持排序、事务、管道、分区等Redis特性。因此项目中选择了jedis作为java客户端。依赖如下:


    redis.clients
    jedis
    ${redis.version}


    org.springframework.data
    spring-data-redis
    ${spring.redis.version}

redis相关操作工具类

@Component
public class RedisUtil {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    private HashOperations hashOps;

    private ListOperations listOps;

    // private SetOperations setOps;

    private ValueOperations valueOps;

    private ZSetOperations zsetOps;


    @PostConstruct
    public void setUp() {
        hashOps = stringRedisTemplate.opsForHash();
        listOps = stringRedisTemplate.opsForList();
        // setOps = stringRedisTemplate.opsForSet();
        valueOps = stringRedisTemplate.opsForValue();
        zsetOps = stringRedisTemplate.opsForZSet();
    }

    /**
     * key是否存在
     * 
     * @param key
     * @return
     */
    public boolean hasKey(String key) {
        return stringRedisTemplate.hasKey(key);
    };

    /**
     * 
     * @param key
     * @param hashKey
     * @return
     */
    public boolean hasHashKey(String key, String hashKey) {
        return hashOps.hasKey(key, hashKey);
    };

    /**
     * 删除key
     * 
     * @param keys
     */
    public void deleteKey(String... keys) {
        stringRedisTemplate.delete(Arrays.asList(keys));
    }

    /**
     * 通过key删除数据
     * 
     * @param key
     */
    public void deleteByKey(String key) {
        stringRedisTemplate.delete(key);
    }

    /**
     * key设置过期时间
     * 
     * @param key
     * @param timeout
     * @param timeUnit
     */
    public void expire(String key, Long timeout, TimeUnit timeUnit) {
        stringRedisTemplate.expire(key, timeout, timeUnit);
    }

    /**
     * 类似keys *
     * 
     * @param pattern
     * @return
     */
    public Collection getKeys(String pattern) {
        return stringRedisTemplate.keys(pattern);
    }

    public static boolean isEnableRedisCache = true;

    /**
     * 是否已缓存key
     * 
     * @param key
     * @return
     */
    public boolean isCached(String key) {
        return isEnableRedisCache && hasKey(key);
    };

    public boolean isCachedHashKey(String key, String hashKey) {
        return isEnableRedisCache && hasHashKey(key, hashKey);
    };

    public final static int EXRP_MINUTE = 60; // 一分钟
    public final static int EXRP_HOUR = 60 * 60; // 一小时
    public final static int EXRP_DAY = 60 * 60 * 24; // 一天
    public final static int EXRP_MONTH = 60 * 60 * 24 * 30; // 一个月
    public final static int EXPR_YEAR = 12 * 60 * 60 * 24 * 30;// 一年

    // 缓存有效期,单位秒
    public static Map expireMap = new HashMap();
    static {
        // key过期设置
        expireMap.put("User", 30 * EXRP_MINUTE);// 30分钟过期

    }

    /**
     * 缓存单条记录
     * 
     * @param 
     * @param id
     * @param o
     */
    public void setSingleObjectInCache(String key, Object obj) {
        if (!isEnableRedisCache) {
            return;
        }

        String singleJson = FastJosnUtils.toJson(obj);
        if (expireMap.containsKey(obj.getClass().getSimpleName())) {
            valueOps.set(key, singleJson,
                    expireMap.get(obj.getClass().getSimpleName()),
                    TimeUnit.SECONDS);
        } else {
            valueOps.set(key, singleJson, expireMap.get("default"),
                    TimeUnit.SECONDS);
        }
    }

    /**
     * 缓存单条记录,并设置过期时间
     * 
     * @param key
     * @param obj
     * @param time
     * @param unit
     */
    public void setSingleObjectInCache(String key, Object obj, Integer time,
            TimeUnit unit) {
        if (!isEnableRedisCache) {
            return;
        }

        String singleJson = FastJosnUtils.toJson(obj);
        valueOps.set(key, singleJson, time, unit);
    }

    /**
     * 根据key获取
     * 
     * @param key
     * @param clazz
     * @return
     */
    public  T getByKeyFromCache(String key,Class clazz) {
        if (!isEnableRedisCache) {
            return null;
        }

        String jsonStr = valueOps.get(key);
        return jsonStr == null ? null : (T)  FastJosnUtils.toObject(jsonStr, clazz);
    }

    /**
     * key的剩余有效期
     * 
     * @param key
     * @return
     */
    public long ttl(String key) {
        return valueOps.getOperations().getExpire(key, TimeUnit.SECONDS);
    }

    /**
     * 缓存一个集合
     * 
     * @param key
     *            键
     * @param collecation
     *            要缓存的集合
     * @param elementClass
     *            集合中类的类型
     */
    public  void setCollectionInCache(String key, Collection collecation,
            Class elementClass) {
        if (!isEnableRedisCache) {
            return;
        }

        // 无论数据是否为空,都推一个key到redis,防止数据为null时不走缓存
        List jsonList = new ArrayList();
        jsonList.add("");

        List list = (List) collecation;
        for (int i = list.size() - 1; i >= 0; i--) {
            jsonList.add(FastJosnUtils.toJson(list.get(i)));
        }

        listOps.getOperations().delete(key);
        listOps.leftPushAll(key, jsonList);

        if (expireMap.containsKey(elementClass.getSimpleName())) {
            listOps.getOperations().expire(key,
                    expireMap.get(elementClass.getSimpleName()),
                    TimeUnit.SECONDS);
        }
    }

    /**
     * 从缓存中取出列表,支持分页
     * 
     * @param 
     * @param 
     * @param id
     * @param obj
     * @return 若没有启用缓存或缓存中没有相应的key,返回null 若缓存中存在key,但value为null,返回空集合对象 example
     *         : new ArrayList()
     */
    @SuppressWarnings("unchecked")
    public , E> T getCollectionInCache(String key,
            Class collectionClass, Class elementClass, int offset,
            int size) {
        if (!isEnableRedisCache) {
            return null;
        }

        if (!hasKey(key)) {
            return null;
        }

        T conllection = (T) new ArrayList();

        List collecationJson = listOps
                .range(key, offset, offset + size);
        if (collecationJson == null || collecationJson.isEmpty()) {
            return conllection;
        }

        for (int i = 0; i < collecationJson.size() - 1; i++) {
            conllection.add((E) FastJosnUtils.toJson(collecationJson.get(i)));
        }

        return conllection;
    }

    /**
     * 缓存一个Map
     * 
     * @param 
     * @param key
     *            键
     * @param collecation
     *            要缓存的集合
     * @param elementClass
     *            集合中类的类型
     */
    public  void setMapInCache(String key, Map map,
            Class elementClass) {
        if (!isEnableRedisCache) {
            return;
        }

        Map jsonMap = new ConcurrentHashMap();
        for (Map.Entry entry : map.entrySet()) {
            jsonMap.put((String) entry.getKey(), FastJosnUtils.toJson(entry.getValue()));
        }

        hashOps.getOperations().delete(key);
        hashOps.putAll(key, jsonMap);
    }

    /**
     * 添加元素到有序集合
     * 
     * @param key
     * @param obj
     * @param score
     */
    public void zSet(String key, Object obj, double score) {
        if (!isEnableRedisCache) {
            return;
        }

        zsetOps.add(key, FastJosnUtils.toJson(obj),score);
    }

    /**
     * 获取有序集合中指定key-value的分数
     * @param key
     * @param obj
     * @return
     */
    public Double  getScore(String key,Object obj) {
        return zsetOps.score(key, obj);
    }

    /**
     * 从有序集合中删除元素
     * @param key
     * @param values
     * @return
     */
    public Long remove(String key, Object... values){
        return null;
    }

}

你可能感兴趣的:(redis入门第十课:实战之缓存)