湖南众诚建设 官方网站,wordpress 煎蛋评论,贵州省住房城乡建设厅网站,网站做动态图片1、布隆过滤
1.1、简介 由一个初值都为零的bit数组和多个哈希函数构成#xff0c;可以用来快速判断集合中是否存在某个元素#xff0c;减少占用内存#xff0c;不保存数据信息#xff0c;只是在内存中做出一个标记。 它实际上是一个很长的二进制数组(00000000)一系列随机h…1、布隆过滤
1.1、简介 由一个初值都为零的bit数组和多个哈希函数构成可以用来快速判断集合中是否存在某个元素减少占用内存不保存数据信息只是在内存中做出一个标记。 它实际上是一个很长的二进制数组(00000000)一系列随机hash算法映射函数主要用于判断一个元素是否在集合中。通常我们会遇到很多要判断一个元素是否在某个集合中的业务场景一般想到的是将集合中所有元素保存起来然后通过比较确定。 使用布隆过滤的话一个元素如果判断结果存在时元素不一定存在但是判断结果为不存在时则一定不存在。布隆过滤器可以添加元素但是不能删除元素由于涉及hashcode判断依据删掉元素会导致误判率增加。 布隆过滤器(Bloom Filter) 是一种专门用来解决去重问题的高级数据结构。实质就是一个大型*位数组*和几个不同的无偏hash函数(无偏表示分布均匀)。由一个初值都为零的bit数组和多个个哈希函数构成用来快速判断某个数据是否存在。但是跟 HyperLogLog 一样它也一样有那么一点点不精确也存在一定的误判概率 1.2、使用特点 添加key时使用多个hash函数对key进行hash运算得到一个整数索引值对位数组长度进行取模运算得到一个位置每个hash函数都会得到一个不同的位置将这几个位置都置1就完成了add操作。 当有变量被加入集合时通过N个映射函数将这个变量映射成位图中的N个点把它们置为 1假定有两个变量都通过 3 个映射函数。 查询key时只要有其中一位是零就表示这个key不存在但如果都是1则不一定存在对应的key。如果这些点有任何一个为零则被查询变量一定不在如果都是 1则被查询变量很可能存在为什么说是可能存在而不是一定存在呢那是因为映射函数本身就是散列函数散列函数是会有碰撞的。见上图3号坑两个对象都1 此外还有可能出现的误差是因为哈希函数的概念是将任意大小的输入数据转换成特定大小的输出数据的函数转换后的数据称为哈希值或哈希编码也叫散列值。 如果两个散列值是不相同的根据同一函数那么这两个散列值的原始输入也是不相同的。
这个特性是散列函数具有确定性的结果具有这种性质的散列函数称为单向散列函数。 散列函数的输入和输出不是唯一对应关系的如果两个散列值相同两个输入值很可能是相同的但也可能不同这种情况称为“散列碰撞collision”。用 hash表存储大数据量时空间效率还是很低当只有一个 hash 函数时还很容易发生哈希碰撞。
1.3、使用步骤
1.3.1、初始化bitmap 布隆过滤器 本质上 是由长度为 m 的位向量或位列表仅包含 0 或 1 位值的列表组成最初所有的值均设置为 0。 1.3.2、添加占坑位
当我们向布隆过滤器中添加数据时为了尽量地址不冲突会使用多个 hash 函数对 key 进行运算算得一个下标索引值然后对位数组长度进行取模运算得到一个位置每个 hash 函数都会算得一个不同的位置。再把位数组的这几个位置都置为 1 就完成了 add 操作。
例如我们添加一个字符串wmyskxz对字符串进行多次hash(key) → 取模运行→ 得到坑位 1.3.3、判断是否存在
向布隆过滤器查询某个key是否存在时先把这个 key 通过相同的多个 hash 函数进行运算查看对应的位置是否都为 1只要有一个位为零那么说明布隆过滤器中这个 key 不存在如果这几个位置全都是 1那么说明极有可能存在因为这些位置的 1 可能是因为其他的 key 存在导致的也就是前面说过的hash冲突。。。。。
就比如我们在 add 了字符串wmyskxz数据之后很明显下面1/3/5 这几个位置的 1 是因为第一次添加的 wmyskxz 而导致的此时我们查询一个没添加过的不存在的字符串inexistent-key它有可能计算后坑位也是1/3/5 这就是误判了 2、缓存问题
2.1、缓存预热 Redis缓存预热是指在服务器启动或应用程序启动之前将一些数据先存储到Redis中以提高Redis的性能和数据一致性。这可以减少服务器在启动或应用程序启动时的数据传输量和延迟从而提高应用程序的性能和可靠性。 1数据准备 在应用程序启动或服务器启动之前准备一些数据这些数据可以是静态数据、缓存数据或其他需要预热的数据。
2数据存储 将数据存储到Redis中可以使用Redis的列表List数据类型或集合Set数据类型。
3数据预热 在服务器启动或应用程序启动之前将数据存储到Redis中。可以使用Redis的客户端工具或命令行工具来执行此操作。
4数据清洗 在服务器启动或应用程序启动之后可能会对存储在Redis中的数据进行清洗和处理。例如可以删除过期的数据、修改错误的数据等。 需要注意的是Redis缓存预热可能会增加服务器的开销因此应该在必要时进行。同时为了减少预热的次数可以考虑使用Redis的其他数据类型如哈希表Hash或有序集合Sorted Set。此外为了提高数据一致性和性能可以使用Redis的持久化功能将数据存储到Redis中并在服务器重启后自动恢复数据。
2.2、缓存雪崩 缓存雪崩是指在同一时间大量的缓存key同时失效或者Redis的服务器宕机然后大量的请求到达数据库然后带来很大的压力。
2.2.1、解决方法
2.2.1.1、 设置有效期均匀分布 将缓存的有效期设置为均匀分布避免大量缓存同时失效。大概的意思就是在缓存数据的时候不要将缓存的数据存在Redis中的设置的过期是时间统一应该让这些缓存的数据的过期时间分散开来这样就不会导致在同一时间有大量的key过期。
2.2.1.2、 数据预热 在系统上线前将可能会访问的数据预先加载到缓存中避免缓存冷启动这个做法还是有必要的因为这样可以避免大量访问请求没有命中Redis后去访问数据库。
2.2.1.3、 保证Redis服务高可用 保证Redis服务的高可用性避免Redis宕机的情况这个方式也很好理解Redis可以配置哨兵用来监控Master主机的是否可以正常工作如果宕机选出新的主机然后如果是想要提高读写性能的话也可以配置成集群的模式多台主机可以执行写操作即使某一台主机挂掉然后还有其他的主机可以执行。
2.2.1.4、多级缓存
2.2.1.5、服务降级
2.3、缓存穿透 首先说一下缓存穿透简单的讲就是请求的数据首先在Redis中查询没有找到然后请求数据库去访问数据但是在数据库中也没有找到但是用户知道自己查询的数据不存在所以此后的所有的请求的压力都会到数据库中 但是事实上请求也没有预期的结果。
2.3.1、解决方法
2.3.1.1、 业务层校验 在缓存中没有命中的情况下可以在业务层进行校验如果校验不通过直接返回错误信息避免继续访问数据库。 还有一种做法就是没有命中Redis和数据库这时候后就可以使用null来应对这种情况大概的思路就是当第一次访问数据库时如果没有数据的话然后将null存入Redis 这样就可以解决下一次数据命中的是Redis而不是数据库然后判断Redis命中如果是空值的话就要返回错误信息但是对于这种的空值的设置的话可以设置超时时间自动清除不建议设置的时间过长 这样会导致数据的不一致的时间过长如果数据库中的数据更新但是Redis中的数据还是null导致请求的参数无法获取正确值这种方式的优点就是简单方便操作但是这样会额外消耗内存以及数据不一致(所以要设置过期时间短一点) 。 还有对于一些访问数据的格式做出校验不要让别人直接可以猜到数据然后直接大量的请求进行访问。此外是对于用户的权限验证。没有权限就直接拦截不让他访问。
2.3.1.2、 布隆过滤器 布隆过滤器是一种数据结构可以用于判断一个元素是否在一个集合中。在缓存中没有命中的情况下可以使用布隆过滤器判断该数据是否存在如果不存在直接返回错误信息。 #初始化
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
/*** auther zzyy* create 2022-12-27 14:55* 布隆过滤器白名单初始化工具类一开始就设置一部分数据为白名单所有* 白名单业务默认规定布隆过滤器有redis也有。*/
Component
Slf4j
public class BloomFilterInit
{Resourceprivate RedisTemplate redisTemplate;
PostConstruct//初始化白名单数据故意差异化数据演示效果......public void init(){//白名单客户预加载到布隆过滤器String uid customer:12;//1 计算hashcode由于可能有负数直接取绝对值int hashValue Math.abs(uid.hashCode());//2 通过hashValue和2的32次方取余后获得对应的下标坑位long index (long) (hashValue % Math.pow(2, 32));log.info(uid 对应------坑位index:{},index);//3 设置redis里面bitmap对应坑位该有值设置为1redisTemplate.opsForValue().setBit(whitelistCustomer,index,true);}
}
#utils
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/*** auther zzyy* create 2022-12-27 14:56*/
Component
Slf4j
public class CheckUtils
{Resourceprivate RedisTemplate redisTemplate;
public boolean checkWithBloomFilter(String checkItem,String key){int hashValue Math.abs(key.hashCode());long index (long) (hashValue % Math.pow(2, 32));boolean existOK redisTemplate.opsForValue().getBit(checkItem, index);log.info(-----key:key\t对应坑位index:index\t是否存在:existOK);return existOK;}
}
#controller
import com.atguigu.redis7.entities.Customer;
import com.atguigu.redis7.service.CustomerSerivce;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Random;
import java.util.Date;
import java.util.concurrent.ExecutionException;
/*** auther zzyy* create 2022-07-23 13:55*/
Api(tags 客户Customer接口布隆过滤器讲解)
RestController
Slf4j
public class CustomerController
{Resource private CustomerSerivce customerSerivce;
ApiOperation(数据库初始化2条Customer数据)RequestMapping(value /customer/add, method RequestMethod.POST)public void addCustomer() {for (int i 0; i 2; i) {Customer customer new Customer();
customer.setCname(customeri);customer.setAge(new Random().nextInt(30)1);customer.setPhone(1381111xxxx);customer.setSex((byte) new Random().nextInt(2));customer.setBirth(Date.from(LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant()));
customerSerivce.addCustomer(customer);}}
ApiOperation(单个用户查询按customerid查用户信息)RequestMapping(value /customer/{id}, method RequestMethod.GET)public Customer findCustomerById(PathVariable int id) {return customerSerivce.findCustomerById(id);}
ApiOperation(BloomFilter案例讲解)RequestMapping(value /customerbloomfilter/{id}, method RequestMethod.GET)public Customer findCustomerByIdWithBloomFilter(PathVariable int id) throws ExecutionException, InterruptedException{return customerSerivce.findCustomerByIdWithBloomFilter(id);}
}
#service
import com.atguigu.redis7.entities.Customer;
import com.atguigu.redis7.mapper.CustomerMapper;
import com.atguigu.redis7.utils.CheckUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
/*** auther zzyy* create 2022-07-23 13:55*/
Service
Slf4j
public class CustomerSerivce
{public static final String CACHE_KEY_CUSTOMER customer:;
Resourceprivate CustomerMapper customerMapper;Resourceprivate RedisTemplate redisTemplate;
Resourceprivate CheckUtils checkUtils;
public void addCustomer(Customer customer){int i customerMapper.insertSelective(customer);
if(i 0){//到数据库里面重新捞出新数据出来做缓存customercustomerMapper.selectByPrimaryKey(customer.getId());//缓存keyString keyCACHE_KEY_CUSTOMERcustomer.getId();//往mysql里面插入成功随后再从mysql查询出来再插入redisredisTemplate.opsForValue().set(key,customer);}}
public Customer findCustomerById(Integer customerId){Customer customer null;
//缓存key的名称String keyCACHE_KEY_CUSTOMERcustomerId;
//1 查询rediscustomer (Customer) redisTemplate.opsForValue().get(key);
//redis无进一步查询mysqlif(customernull){//2 从mysql查出来customercustomercustomerMapper.selectByPrimaryKey(customerId);// mysql有redis无if (customer ! null) {//3 把mysql捞到的数据写入redis方便下次查询能redis命中。redisTemplate.opsForValue().set(key,customer);}}return customer;}
/*** BloomFilter → redis → mysql* 白名单whitelistCustomer* param customerId* return*/
Resourceprivate CheckUtils checkUtils;public Customer findCustomerByIdWithBloomFilter (Integer customerId){Customer customer null;
//缓存key的名称String key CACHE_KEY_CUSTOMER customerId;
//布隆过滤器check无是绝对无有是可能有//if(!checkUtils.checkWithBloomFilter(whitelistCustomer,key)){log.info(白名单无此顾客信息:{},key);return null;}//
//1 查询rediscustomer (Customer) redisTemplate.opsForValue().get(key);//redis无进一步查询mysqlif (customer null) {//2 从mysql查出来customercustomer customerMapper.selectByPrimaryKey(customerId);// mysql有redis无if (customer ! null) {//3 把mysql捞到的数据写入redis方便下次查询能redis命中。redisTemplate.opsForValue().set(key, customer);}}return customer;}
} 比如设置白名单或者每日签到再者是打卡等等在数据存储的时候可以将该数据的key使用hash函数获取索引然后结合bitmap将该索引的位设置成为true1然后在数据读取的时候首先在布隆过滤器中拦截查询的key并且计算出这个key的索引值然后读取redis中的bitmap结构中该索引位置的数据是true还是false如果为true说明可能存在这个key所对应的数据但是如果为false则说明这个key一定不存在直接拦截
2.3.1.3、Guava布隆过滤器
import com.atguigu.redis7.service.GuavaBloomFilterService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/*** auther zzyy* create 2022-12-30 16:50*/
Api(tags google工具Guava处理布隆过滤器)
RestController
Slf4j
public class GuavaBloomFilterController
{Resourceprivate GuavaBloomFilterService guavaBloomFilterService;
ApiOperation(guava布隆过滤器插入100万样本数据并额外10W测试是否存在)RequestMapping(value /guavafilter,method RequestMethod.GET)public void guavaBloomFilter(){guavaBloomFilterService.guavaBloomFilter();}
}
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
/*** auther zzyy* create 2022-12-30 16:50*/
Service
Slf4j
public class GuavaBloomFilterService{public static final int _1W 10000;//布隆过滤器里预计要插入多少数据public static int size 100 * _1W;//误判率,它越小误判的个数也就越少(思考是不是可以设置的无限小没有误判岂不更好)//fpp the desired false positive probabilitypublic static double fpp 0.03;// 构建布隆过滤器private static BloomFilterInteger bloomFilter BloomFilter.create(Funnels.integerFunnel(), size,fpp);public void guavaBloomFilter(){//1 先往布隆过滤器里面插入100万的样本数据for (int i 1; i size; i) {bloomFilter.put(i);}//故意取10万个不在过滤器里的值看看有多少个会被认为在过滤器里ListInteger list new ArrayList(10 * _1W);for (int i size1; i size (10 *_1W); i) {if (bloomFilter.mightContain(i)) {log.info(被误判了:{},i);list.add(i);}}log.info(误判的总数量:{},list.size());}
}
2.4、缓存击穿 缓存击穿问题也叫做热点key问题就是一个高并发访问并且缓存重建业务比较复杂的key突然失效啦无数的请求访问会在瞬间给数据库带来巨大的冲击。
2.4.1、解决方法
2.4.1.1、设置热点数据永不过期 将热点数据设置为永不过期这样可以避免热点数据失效的情况既然你是热点key而且我还害怕你过期奶奶的给你设置为永不过期
2.4.1.2、差异失效时间 这种机制的话可以设置一个缓存的过期时间的不一致性就是可以实现有时间的间隔来执行缓存更新数据即使在某一时刻a缓存过期了还有b缓存用于命中旧的数据
2.4.1.3、互斥锁 在热点数据失效的情况下可以使用互斥锁保证只有一个线程去访问数据库其他线程等待。相较于原来从缓存中查询不到数据后直接查询数据库而言现在的方案是 进行查询之后如果从缓存没有查询到数据则进行互斥锁的获取获取互斥锁后判断是否获得到了锁如果没有获得到则休眠过一会再进行尝试直到获取到锁为止才能进行查询 如果获取到了锁的线程再去进行查询查询后将数据写入redis再释放锁返回数据利用互斥锁就能保证只有一个线程去执行操作数据库的逻辑防止缓存击穿 2.4.1.4、逻辑过期 当用户开始查询redis时判断是否命中如果没有命中则直接返回空数据不查询数据库而一旦命中后将value取出判断value中的过期时间是否满足如果没有过期则直接返回redis中的数据如果过期则在开启独立线程后直接返回之前的数据独立线程去重构数据重构完成后释放互斥锁。 3、Redis锁 在单机的系统中同一个JVM虚拟机内可以使用synchronized或者lock接口来实现锁的功能但是对于分布式的功能来讲里面有者不同的JVM虚拟机这就到导致之前的在JVM内部的锁的无法使用所以我们要能够有一种分布式的锁来完成这种功能。
那么要成为一个靠谱的分布式的锁需要具备那些的前提条件 独占性 任何时刻有且只有一个线程所持有 高可用 在redis集群的环境下不能出现某一个节点挂掉而出现获取锁和释放锁失败的情况在高并发的情况下依然耐造 防死锁 杜绝死锁必须有超时控制机制或者撤销机制有一个兜底终止跳出的方案 不乱抢 自己的锁自己释放不能二话不说释放别人的锁 重入性 同一个节点的同一个线程如果获取锁之后它可以再次获取这个锁
3.1、使用单机的redis锁
public String sale(){String retMessage ;lock.lock();try{//1 查询库存信息String result stringRedisTemplate.opsForValue().get(inventory001);//2 判断库存是否足够Integer inventoryNumber result null ? 0 : Integer.parseInt(result);//3 扣减库存if(inventoryNumber 0) {stringRedisTemplate.opsForValue().set(inventory001,String.valueOf(--inventoryNumber));retMessage 成功卖出一个商品库存剩余: inventoryNumber;System.out.println(retMessage);}else{retMessage 商品卖完了o(╥﹏╥)o;}}finally {lock.unlock();}return retMessage\t服务端口号port;}
使用nginx配置负载均衡暂时没有学习先使用后补课 重新加载配置文件后启动服务器然后使用jmeter进行高并发的测试。创建线程组以及多少线程和持续时间然后配置http请求启动后看测式结果。我们再redsi中存储100个数据然后两台服务器按道理说最后执行完毕刚好全部卖完但是事实上redis中还剩余11个另外在控制台中可以看出有卖出重复票的情况。 那么使用单机锁来应对分布式高并发的项目来讲就会出现锁不管用的情况。在单机环境下可以使用synchronized或Lock来实现。 但是在分布式系统中因为竞争的线程可能不在同一个节点上同一个jvm中所以需要一个让所有进程都能访问到的锁来实现(比如redis或者zookeeper来构建)。不同进程jvm层面的锁就不管用了那么可以利用第三方的一个组件来获取锁未获取到锁则阻塞当前想要运行的线程。
3.2、分布式Redis锁
3.2.1、使用递归的方式实现