并发编程
我们把一个任务称为一个进程,浏览器,qq,音乐播放器,word都是进程某些进程内部还需要同时执行多个子任务,我们把子任务就称之为线程, 它俩的关系就是,一个进程可以包含一个或多个线程,至少有一个线程
ThreadLocal是什么?
即线程本地变量,如果你创建了一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个本地拷贝,多个线程操作这个变量的时候,实际是在操作自己本地内存里面的变量,从而起到线程隔离的作用,避免并发场景下的线程安全问题
为什么要使用ThreadLocal?
并发场景下.会存在多个线程同时修改一个共享变量的场景,这就可能会出现线程安全问题
为了解决线程安全问题,可以用加锁的方式,比如使用synchronized或Lock,但是加锁的方式可能会导致系统变慢,所以另一种方案,就是使用空间换时间的方式,即使用ThreadLoad,使用ThreadLocal类访问共享变量时,会在每个线程的本地,都保存一份共享变量的拷贝副本,多线程对共享变量修改时,实际上操作的是这个变量副本,从而保证线程安全
日常开发中
- 使用日期工具类,当用到
SimpleDateFormat,使用ThreadLocal保证线性安全
ThreadLocal的实现原理
ThreadLocalMap内部维护着Entry数组,每个Entry代表一个完整的对象,key是ThreadLocal本身,value是ThreadLocal的泛型值。- 并发多线程场景下,每个线程
Thread,在往ThreadLocal里设置值的时候,都是往自己的ThreadLocalMap里存,读也是以某个ThreadLocal作为引用,在自己的map里找对应的key,从而可以实现了线程隔离。
TreadLocal为什么会导致内存泄漏呢?
ThreadLocalMap使用ThreadLocal的弱引用作为key,当ThreadLocal变量被手动设置为null,即一个ThreadLocal没有外部强引用来引用它,当系统GC时,ThreadLocal一定会被回收。这样的话,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,如果当前线程再迟迟不结束的话(比如线程池的核心线程),这些key为null的Entry的value就会一直存在一条强引用链:Thread变量 -> Thread对象 -> ThreaLocalMap -> Entry -> value -> Object 永远无法回收,造成内存泄漏。
实际上,ThreadLocalMap的设计中已经考虑到这种情况。所以也加上了一些防护措施:即在ThreadLocal的get,set,remove方法,都会清除线程ThreadLocalMap里所有key为null的value
什么是Redis?它主要用来干什么?
是一个开源的使用ANSI C语言编写,支持网络,可基内存持久化的日志型,Key-Value数据库
与MySQL数据库不同的是,Redis的数据是存在内存中的. 他的读写速度非常快.,每秒可以处理超过10万次读写操作,所以Redis被广泛应用于缓存; 也经常用来做分布式锁;他还支持事务,持久化,LUA脚本,LRU驱动事件,多种集群方案
Redis的基本数据结构类型
String(SDS简单动态字符串), Hash(哈希),List(列表),set(集合), zset(有序集合)
特殊的数据结构类型
Geospatial, Hyperloglog, Bitmap
什么是I/O多路复用
多个网络连接,复用同一个线程; 其实就是一种同步IO模型, 它实现了一个线程可以监视多个文件句柄;一旦某个文件句柄就绪,就能够通知应用程序进行相应的读写操作;而没有文件句柄就绪时,就会阻塞应用程序,交出CPU
虚拟内存机制
就是暂时把不经常访问的数据(冷数据)从内存交换到磁盘中,从而腾出宝贵的内存空间用于其他需要访问的数据(热数据). 通过VM功能可以实现冷热数据分离,使热数据扔在内存中,冷数据保存到磁盘,这样就可以避免因为内存不足而造成访问速度下降的问题
什么是缓存穿透,缓存击穿,缓存雪崩?
常见的使用缓存方式: 读请求来了,先查下缓存,缓存有值命中,就直接返回;缓存没命中,就去查数据库,然后把数据库的值更新到缓存,再返回
缓存穿透:读请求访问时,缓存和数据库都没有这个值,这样就会导致每次对这个值的查询请求都会穿透到数据库
缓存穿透一般都是这几种情况产生的:
- 业务/运维/开发失误的操作,比如缓存和数据库的数据都被误删除了。
- 黑客非法请求攻击,比如黑客故意捏造大量非法请求,以读取不存在的业务数据。
如何避免缓存穿透呢? 一般有三种方法。
- 如果是非法请求,我们在API入口,对参数进行校验,过滤非法值
- 如果查询数据库为空,我们可以给缓存设置个空值,或默认值,设置一个较短的过期时间,让其自动剔除
- 使用布隆过滤器快速判断数据是否存在,即一个查询请求过来时,先通过布隆过滤器判断值是否存在,存在才继续往下查
布隆过滤器原理:它由初始值为0的位图数组和N个哈希函数组成。一个对一个key进行N个hash算法获取N个值,在比特数组中将这N个值散列后设定为1,然后查的时候如果特定的这几个位置都为1,那么布隆过滤器判断该key存在。
缓存击穿: 缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),而恰好在这个时间点对这个key有大量的并发请求过来,从而大量的请求打到DB数据库; 击穿是针对某一热点key缓存, 雪崩则是很多key
解决方案:
-
使用互斥锁方案:缓存失效时,不是立即去加载DB数据,而是先使用某些带成功返回的原子操作命令
setnx去操作,成功的时候,再去加载DB数据库数据和设置缓存,否则就重试获取缓存
-
永不过期,是指没有设置过期时间,但是热点数据快要过期时,异步线程去更新和设置过期时间
缓存雪崩: 指缓存中数据大批量到了过期时间,而查询数据量巨大,请求都直接访问数据库了,就会引起数据库压力过大甚至down机;和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
解决方案:
- 可通过均匀(随机)设置过期时间解决,即让过期时间相对离散一点,减少缓存在同一时间过期的概率
- 如果缓存数据库是分布式部署,将热点数据均匀分布在不同的缓存数据库中。
- redis故障宕机也可能引起缓存雪崩,这就需要构造Redis高可用集群了
缓存污染
缓存污染问题说的是缓存中一些只会被访问一次或者几次的的数据,被访问完后,再也不会被访问到,但这部分数据依然留存在缓存中,消耗缓存空间。
写的时候判断淘汰策略,根据淘汰策略去选择要淘汰的数据,然后进行删除操作
什么是热Key呢?在Redis中,我们把访问频率高的key,称为热点key。
如何解决热key问题?
- Redis集群扩容:增加分片副本,均衡读流量;
- 将热key分散到不同的服务器中;
- 使用二级缓存,即JVM本地缓存,减少Redis的读请求。
Redis过期策略(定时过期,惰性过期,定期过期)
定时过期:每个设置过期时间的key都需要创建一个定时器,到过期时间就会立即对key进行清除.该策略可以立即清除过期的数据,对内存很友好;但是会占用大量的CPU资源去处理过期的数据,从而影响缓存的响应时间和吞吐量
惰性过期:只有当访问一个key时,才会判断该key是否已过期,过期则清除;该策略可以最大化地节省了CPU资源,但对内存不友好; 极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存
定期过期:每隔一定的时间,会扫描一定数量的数据库的expires字典中一定数量的key,并清除其中已过期的key.该策略是前两者的一个折中方案.通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得CPU和内存资源达到最优的平衡效果
expires字典会保存所有设置了过期时间的key的过期时间数据
redis中同时使用了惰性和定期过期两种策略
假设Redis当前存放30万个key,并且都设置了过期时间,如果你每隔100ms就去检查这全部的key,CPU负载会特别高,最后可能会挂掉; 因此redis采取的是定期过期,每隔100ms就随机抽取一定数量的key来检查和删除的. 但是最后可能会有很多已经过期的key没被删除. 这是采用惰性过期策略,在你获取某个key的时候,redis会检查一下,这个key如果设置了过期时间并且已经过期了,此时就会删除; 但是,如果定期删除漏掉了好多过期的key,然后也没走惰性删除,就会有很多过期key积在内存,就会导致内存爆炸; 这样Redis就会直接挂掉吗??? 不会的,Redis用了8中内存淘汰策略保护自己
Redis内存淘汰策略
volatile-lru:当内存不足以容纳新写入数据时,从设置了过期时间的key中使用LRU(最近最少使用)算法进行淘汰;
volatile-lfu:4.0版本新增,从设置了过期时间的key中,使用LFU算法进行删除key;
volatile-random:从设置了过期时间的key中,随机淘汰数据。
allkeys-lru:从所有key中使用LRU(最近最少使用)算法进行淘汰。
allkeys-lfu:4.0版本新增,从所有key中使用LFU算法进行淘汰;
allkeys-random:从所有key中随机淘汰数据。
volatile-ttl:在设置了过期时间的key中,根据过期时间进行淘汰,越早过期的优先被淘汰;
noeviction:默认策略,当内存不足以容纳新写入数据时,新写入操作会报错。
Redis的常用应用场景
缓存,排行榜,计数器应用,共享session,分布式锁,社交网络,消息队列,位操作
Redis的持久化机制(RDB和AOF)
Redis是基于内存的非关系型数据库,既然它是基于内存的,如果Redis服务器挂了,数据就会丢失,为了避免数据丢失了,redis提供了持久化,即把数据保存到磁盘
RDB:就是把内存中的数据以快照的形式保存到磁盘上
什么是快照?可以这样理解,给当前时刻的数据,拍一张照片,然后保存下来。
如果是15号,跑当月1-15号的数据,查询基本信息表中在职的人员;查询变动记录表中异动类型是ZA的(离职),并根据离职记录表中的异动时间和旷离时间筛选2-15号离职的人员;如果旷离时间为空,则判断异动时间是否在查询范围内,不为空则判断旷离时间是否在范围内,过滤完后进行快照
如果是1号,跑上个月16号到月底的数据;查询变动记录表中16-月底入职(Z1,Z2)的人员进行快照