解决redis三大问题

    科技2022-07-11  92

    1、缓存穿透

    解决方案:

    1.1、布隆过滤器

    原理:核心是一个bitmap(位数组),初始值都是0,用k个hash函数对某个key进行哈希,哈希出来的值对数组长度取模,取模出来的值就是bitmap位数组的下标,将这个下标改为1。例如有三个hash函数,其中一个hash函数对某个key哈希出来的值是6354719,然后对数组长度取模,比如数组长度为20,则6354719 =19,则将bitmap位数组的19下标改为1。

    容错率:实则就是误判的概率。跟数组长度、hash函数的个数有关,必需配合使用。容错率越小,创建数组长度越大,性能越低。误判解释如下几个图所示:

    解释:当往布隆过滤器中添加了1-20的数字时,布隆过滤器中黄色的下标是已经被改为1了,此时位数组长度差不多被改满了。而输入一个不存在布隆过滤器中的数据,比如说111时,它却说可能存在,所以此时出现了误判。

    维护:添加表记录的时候往布隆过滤器添加一份;表中如果删除了大量的数据应重建布隆过滤器。

    优点: 相比于其它的数据结构,布隆过滤器在空间和时间方面都有巨大的优势。布隆过滤器存储空间和查询时间都是常数({\displaystyle O(k)} )。另外,散列函数相互之间没有关系,方便由硬件并行实现。布隆过滤器不需要存储元素本身,在某些对保密要求非常严格的场合有优势。

    缺点: 但是布隆过滤器的缺点和优点一样明显。误判率是其中之一。随着存入的元素数量增加,误算率随之增加。但是如果元素数量太少,则使用散列表足矣。

    实现:

    package com.luban.testcache.filter; import com.google.common.hash.Funnels; import com.google.common.hash.Hashing; import lombok.Data; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Scope; import org.springframework.dao.DataAccessException; import org.springframework.data.redis.connection.RedisConnection; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisCallback; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.lang.Nullable; import org.springframework.stereotype.Component; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import redis.clients.jedis.Pipeline; import javax.annotation.PostConstruct; import java.nio.charset.Charset; import java.util.List; @ConfigurationProperties("bloom.filter") @Component public class RedisBloomFilter { //预计插入量 private long expectedInsertions; //可接受的错误率 private double fpp; @Autowired private RedisTemplate redisTemplate; //bit数组长度 private long numBits; //hash函数数量 private int numHashFunctions ; public long getExpectedInsertions() { return expectedInsertions; } public void setExpectedInsertions(long expectedInsertions) { this.expectedInsertions = expectedInsertions; } public void setFpp(double fpp) { this.fpp = fpp; } public double getFpp() { return fpp; } @PostConstruct public void init(){ this.numBits = optimalNumOfBits(expectedInsertions, fpp); this.numHashFunctions = optimalNumOfHashFunctions(expectedInsertions, numBits); } //计算hash函数个数 private int optimalNumOfHashFunctions(long n, long m) { return Math.max(1, (int) Math.round((double) m / n * Math.log(2))); } //计算bit数组长度 private long optimalNumOfBits(long n, double p) { if (p == 0) { p = Double.MIN_VALUE; } return (long) (-n * Math.log(p) / (Math.log(2) * Math.log(2))); } /** * 判断keys是否存在于集合 */ public boolean isExist(String key) { long[] indexs = getIndexs(key); List list = redisTemplate.executePipelined(new RedisCallback<Object>() { @Nullable @Override public Object doInRedis(RedisConnection redisConnection) throws DataAccessException { redisConnection.openPipeline(); for (long index : indexs) { redisConnection.getBit("bf:taibai".getBytes(), index); } redisConnection.close(); return null; } }); return !list.contains(false); } /** * 将key存入redis bitmap */ public void put(String key) { long[] indexs = getIndexs(key); redisTemplate.executePipelined(new RedisCallback<Object>() { @Nullable @Override public Object doInRedis(RedisConnection redisConnection) throws DataAccessException { redisConnection.openPipeline(); for (long index : indexs) { redisConnection.setBit("bf:taibai".getBytes(),index,true); } redisConnection.close(); return null; } }); } /** * 根据key获取bitmap下标 */ private long[] getIndexs(String key) { long hash1 = hash(key); long hash2 = hash1 >>> 16; long[] result = new long[numHashFunctions]; for (int i = 0; i < numHashFunctions; i++) { long combinedHash = hash1 + i * hash2; if (combinedHash < 0) { combinedHash = ~combinedHash; } result[i] = combinedHash % numBits; } return result; } /** * 获取一个hash值 */ private long hash(String key) { Charset charset = Charset.forName("UTF-8"); return Hashing.murmur3_128().hashObject(key, Funnels.stringFunnel(charset)).asLong(); } }

    使用:容器启动时将数据库中表数据put到布隆过滤器中

    @PostConstruct public void init(){ List<Order> orders = orderService.selectOrderyAll(); for (Order order : orders) { redisBloomFilter.put(String.valueOf(order.getId())); } }

    1.2、将一些查询不到的值设为null。

    2、缓存击穿

    2.1、解决方案:使用分布式锁。

    /** * 分布式锁解决缓存击穿 * @param key 键 * @return */ public String getKey(Integer key) { String value = (String)redisTemplate.opsForValue().get(key+""); if(value == null){ String mutexKey = "mutex:key:"+key; //设置分布式锁的key if(redisTemplate.opsForValue().setIfAbsent(mutexKey,"1",180, TimeUnit.SECONDS)){ //给这个key上一把分布式锁,ex表示只有一个线程能执行,过期时间为180秒 // 从数据库中查出该数据放入redis value = userDao.getUserName(key); redisTemplate.opsForValue().set(key+"",value); // 释放锁 redisTemplate.delete(mutexKey); }else{ // 其他的线程休息100毫秒后重试 try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } getKey(key); }} return value; }

    redis.set(mutexKey,“1”,“ex 180”,“nx”)则对应redis中的SET KEY VALUE [EX seconds] [PX milliseconds] [NX|XX]命令,例子如下:

    127.0.0.1:6379> set a 1 ex 10 nx OK 127.0.0.1:6379> ttl a 8

    测试一下:第一次查询,输出了sql语句。 看看redis

    第二次再查询,还是这个样子,什么都没输出,查redis。

    搞定。

    3、缓存雪崩

    原因:redis中key大面积同时失效,导致大量并发请求都打到了数据库上。

    解决方案: 1、设置redis中的key随机的过期时间。 2、配置redis高可用集群(主从复制、读写分离、哨兵、集群等)。

    侵歉删

    Processed: 0.025, SQL: 8