问题:现有50亿个电话号码,现有10万个电话号码,如何要快速准确的判断这些电话号码是否已经存在?
1、通过数据库查询-------实现快速有点难。
2、数据预放到内存集合中:50亿*8字节大约40G,内存太大了。
实质就是一个大型位数组(初值0)和几个不同的无偏hash函数(无偏表示分布均匀)。
当有变量被加入集合时,通过N个映射函数将这个变量映射成位图中的N个点,
把它们置为 1(假定有两个变量都通过 3 个映射函数)。
若现在只存如obj1和obj2,如上图。
假设现在obj3,经过多个hash映射到1、3、8的位置,宏观上看我们知道obj3不存在,但是底层三个位置为1,最后给我的是true,故而只能确定为不一定存在
假设现在obj3,经过多个hash映射到1、3、4的位置,宏观上看我们知道obj4不存在,但是底层三个位置存在0,最后给我的是false,故而判定一定不存在
布隆过滤器误判率,为什么不要删除?(多个元素共享一个位置)
黑名单
在单机环境下,可以使用synchronized或Lock来实现。
但是在分布式系统中,因为竞争的线程可能不在同一个节点上(同一个jvm中),
所以需要一个让所有进程都能访问到的锁来实现(比如redis或者zookeeper来构建)
不同进程jvm层面的锁就不管用了,那么可以利用第三方的一个组件,来获取锁,未获取到锁,则阻塞当前想要运行的线程
分布式部署后,单机锁还是出现超卖现象,需要分布式锁
问题1:若服务异常,无法释放锁,需代码层面finally释放锁
问题2:服务宕机,未执行finally,需给锁添加超时时间
问题3:设置key+过期时间分开了,必须要合并成一行具备原子性
问题4:删除了别人的锁,需 删除时判断是否是自己的锁
当锁的粒度粗,两次请求使用同一把锁,若业务执行时间长,锁过期时间短
第一次请求锁过期,业务还未执行完,此时
第二个请到来获取到锁,最后被第一请求删锁,故删除时需判断是否是自己的锁
此处判断是否是自己的锁根据,锁持有值判断,两次请求值不一样
问题5:finally块的判断+del删除操作不是原子性的
解决方案:Redis调用Lua脚本通过eval命令保证代码执行的原子性
问题6:确保redisLock过期时间大于业务执行时间的问题(Redis分布式锁如何续期?)
确保redisLock过期时间大于业务执行时间的问题(Redis分布式锁如何续期?)
集群+CAP,redis对比zookeeper
Zookeeper集群的CAP:
上述代码解决了续期问题(Redisson),
问题1: 但无锁时执行unlock,报错,需判断
尝试解锁,没锁当前线程节点id
使用场景:多个服务间保证同一时刻同一时间段内同一用户只能有一个请求(防止关键业务出现并发攻击)
Redis分布式锁比较正确的姿势是采用redisson这个客户端工具
基于setnx的分布式锁有什么缺点?解决方案红锁
破坏了锁的排他特性,同一时间只能有一个建redis锁成功并持有锁,严禁出现2个以上的请求线程拿到锁。危险的
Redlock:
redis之父提出了Redlock算法解决这个问题:Redis也提供了Redlock算法,用来实现基于多个实例的分布式锁。锁变量由多个实例维护,即使有实例发生了故障,锁变量仍然是存在的,客户端还是可以完成锁操作。Redlock算法是实现高可靠分布式锁的一种有效解决方案,可以在实际开发中使用。
设计理念:
该方案为了解决数据不一致的问题,直接舍弃了异步复制只使用 master 节点,同时由于舍弃了 slave,为了保证可用性,引入了 N 个节点,官方建议是 5。
解决方案:采用3个说明
为什么是奇数? N = 2X + 1 (N是最终部署机器数,X是容错机器数)
那你知道redis之父设计的这个方案,还有bug吗?分布式难以避免的,系统时钟影响
如果线程1从3个实例获取到了锁。但是这3个实例中的某个实例的系统时间走的稍微快一点,则它持有的锁会提前过期被释放,当他释放后,此时又有3个实例是空闲的,则线程2也可以获取到锁,则可能出现两个线程同时持有锁了。
一般中小公司,不是高并发场景,是可以使用的。单机redis小业务也撑得住
加锁关键逻辑:
NX:等效setNx,key不存在才能加锁成功
解锁关键逻辑:
看门狗是为了解决业务耗时,锁过期失效问题
场景:A、B线程执行同一业务方法,A加分布式锁,锁过期失效,A还没执行完,B获取到锁执行,可能会出现安全问题
解决方法:在获取锁成功后,给锁加一个watchdog后台线程,定期检查(每1/3的锁时间检查1次),如果线程还持有锁,则刷新过期时间
加锁成功触发监听执行如下方法,
这里面初始化了一个定时器,dely 的时间是 internalLockLeaseTime/3。
在 Redisson 中,internalLockLeaseTime 是 30s,也就是每隔 10s 续期一次,每次 30s。
客户端A加锁成功,就会启动一个watch dog看门狗,他是一个后台线程,会每隔10秒检查一下,如果客户端A还持有锁key,那么就会不断的延长锁key的生存时间,默认每次续命又从30秒新开始
当其他线程进入临界资源,发现存在分布式锁,会执行到加锁lua脚本中
“return redis.call(‘pttl’, KEYS[1]);” // 如果已经锁定,但并非本线程,返回锁还有锁失效ttl |
---|
方案1:先更新数据库,再更新缓存
问题分析:更新db成功,更新缓存失败(缓存还是旧的),导致db和缓存数据不一致
方案2:先删除缓存,再更新数据库问题1:请求A删缓存成功,写db业务耗时,此时请求B查询数据,会出现两个问题:
请求B从mysql中获取旧值
请求B获取的旧值写回redis
请求A更新完成,但是缓存缓存中是旧数据
解决方案1:阿里内部缓存击穿的方案
多个线程同时去查询数据库的这条数据,那么我们可以在第一个查询数据的请求上使用一个互斥锁来锁住它。其他的线程走到这一步拿不到锁就等着,等第一个线程查询到了数据,然后做缓存。后面的线程进来发现已经有缓存了,就直接走缓存。
这个删除该休眠多久呢?
统计下线程读数据和写缓存的操作时间,自行评估自己的项目的读数据业务逻辑的耗时,
以此为基础来进行估算。然后写数据的休眠时间则在读数据业务逻辑的耗时基础上加百毫秒即可。
思考:即使写在读上加时间,在高并发下还是不能得到保证的。分析:若读将旧值写入缓存,写更新完db还未删除缓存,由于高并发读在写更新完db和未删除缓存空隙读取就是脏数据。
问题:写操作在读操作基础上加时间,降低了吞吐
解决方案:延时删除采用异步线程
上述效果是mysql单机,如果mysql主从读写分离架构如何?
方案2和方案3用那个?利弊如何
优先使用方案3:先更新数据库,再删除缓存。理由如下:
方案2难点:
落地方案推荐:方案3,使用canal+mq的异步删除
解决方案1:空对象缓存或者缺省值
存在问题:黑客或恶意攻击
大量不同id打击,也会造成数据压力
方案2:布隆过滤器
方案2.1:Google布隆过滤器Guava解决缓存穿透(缺点:单机)
方案2.2:Redis布隆过滤器解决缓存穿透
基于布隆过滤器的白名单架构:
黑名单架构:
热点key失效,导致大量请求打到mysql
方案1:加分布式锁(互斥独占锁防止击穿),获得锁的请求会将查询数据放入缓存,后续请求查缓存
方案2:对于访问频繁的热点key,干脆就不设置过期时间