阶段小结(一)

1.redis的类型了解吗?

String:key-value 
  redis命令不区分大小写,但是key区分的,redis中的数据都是字符串

在这里插入图片描述

Hash: key-field-value
  相当于一个key 对应一个map (map中又是key- value)

在这里插入图片描述

List:
  List是有顺序可重复(数据结构中的:双链表,队列),可作为链表 ,从左添加元素  也可以从右添加元素。
  redis的list类型其实就是一个每个元素都是string类型的双向链表。
  所以lpush、rpush、lpop和rpop命令的时间复杂度是O(1),list会记录链表的长度,所以llen操作也是O(1)的时间复杂度。
  链表的最大长度是2的32次方减1。
  list类型可以用作队列或者栈。
  list还有阻塞版本,就是说如果队列为空,就会等待直到超时或者有数据放入队列。
  阻塞版本的好处是避免轮询,当有数据时,工作线程可以马上返回,避免轮询带来的延时。
Set:
	 Set无顺序,不能重复
     sadd set1 a b c d d (向set1中添加元素) 元素不重复
SortedSet(zset)
      有顺序,不能重复
      适合做排行榜 排序需要一个分数属性
key 命令
expire key second  (设置key的过期时间)
ttl key (查看剩余时间)(-2 表示不存在,-1 表示已被持久化,正数表示剩余的时间)
persist key (清除过期时间,也即是持久化 持久化成功体提示 1 不成功0)。
del key: 删除key 
EXISTS key
      若key存在,返回1,否则返回0。
select 0 表示:选择0号数据库。默认是0号数据库

2.缓存穿透、缓存雪崩、缓存击穿

1.缓存穿透

描述:

  • 缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。

解决方案:

  1. 接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;
  2. 从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击

2.缓存击穿

描述:

  • 缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力

解决方案:

  • 设置热点数据永远不过期。
  • 加互斥锁,互斥锁参考代码如下:
        //从redis中获取数据
      String result =  getDataFromRedis(key);
      //缓存中不存在数据
       if (result == null){
           //去获取锁,获取成功,从数据库取数据
           if (reentrantLock.tryLock()){
               //从数据库获取数据
               result =  getDataFromMysql(key);
               //更新缓存数据
               if (result != null){
                   setDataToCache(key,result);
               }
               //释放锁
               reentrantLock.unlock();
           }else {
               //获取锁失败
               //暂停100ms再去重新获取数据
               Thread.sleep(100);
               result = getData(key);
           }
       }
       return result;
   }
1)缓存中有数据,直接走上述代码2行后就返回结果了
2)缓存中没有数据,第1个进入的线程,获取锁并从数据库去取数据,没释放锁之前,其他并行进入的线程会等待100ms,再重新去缓存取数据。这样就防止都去数据库重复取数据,重复往缓存中更新数据情况出现。
3)当然这是简化处理,理论上如果能根据key值加锁就更好了,就是线程A从数据库取key1的数据并不妨碍线程B取key2的数据,上面代码明显做不到这点。

3.缓存雪崩
描述:

  • 缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。

解决方案:

  1. 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
  2. 如果缓存数据库是分布式部署,将热点数据均匀分布在不同搞得缓存数据库中。
  3. 设置热点数据永远不过期。

3.rdb和aof区别

二者的区别

  1. RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘,实际操作过程是fork一个子进程,先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储。

  2. AOF持久化以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,以文本的方式记录,可以打开文件看到详细的操作记录。

二者优缺点

RDB存在哪些优势呢?

1). 一旦采用该方式,那么你的整个Redis数据库将只包含一个文件,这对于文件备份而言是非常完美的。比如,你可能打算每个小时归档一次最近24小时的数据,同时还要每天归档一次最近30天的数据。通过这样的备份策略,一旦系统出现灾难性故障,我们可以非常容易的进行恢复。

2). 对于灾难恢复而言,RDB是非常不错的选择。因为我们可以非常轻松的将一个单独的文件压缩后再转移到其它存储介质上。

3). 性能最大化。对于Redis的服务进程而言,在开始持久化时,它唯一需要做的只是fork出子进程,之后再由子进程完成这些持久化的工作,这样就可以极大的避免服务进程执行IO操作了。

4). 相比于AOF机制,如果数据集很大,RDB的启动效率会更高。

RDB又存在哪些劣势呢?

1). 如果你想保证数据的高可用性,即最大限度的避免数据丢失,那么RDB将不是一个很好的选择。因为系统一旦在定时持久化之前出现宕机现象,此前没有来得及写入磁盘的数据都将丢失。

2). 由于RDB是通过fork子进程来协助完成数据持久化工作的,因此,如果当数据集较大时,可能会导致整个服务器停止服务几百毫秒,甚至是1秒钟。

AOF的优势有哪些呢?

1). 该机制可以带来更高的数据安全性,即数据持久性。Redis中提供了3中同步策略,即每秒同步、每修改同步和不同步。事实上,每秒同步也是异步完成的,其效率也是非常高的,所差的是一旦系统出现宕机现象,那么这一秒钟之内修改的数据将会丢失。而每修改同步,我们可以将其视为同步持久化,即每次发生的数据变化都会被立即记录到磁盘中。可以预见,这种方式在效率上是最低的。至于无同步,无需多言,我想大家都能正确的理解它。

2). 由于该机制对日志文件的写入操作采用的是append模式,因此在写入过程中即使出现宕机现象,也不会破坏日志文件中已经存在的内容。然而如果我们本次操作只是写入了一半数据就出现了系统崩溃问题,不用担心,在Redis下一次启动之前,我们可以通过redis-check-aof工具来帮助我们解决数据一致性的问题。

3). 如果日志过大,Redis可以自动启用rewrite机制。即Redis以append模式不断的将修改数据写入到老的磁盘文件中,同时Redis还会创建一个新的文件用于记录此期间有哪些修改命令被执行。因此在进行rewrite切换时可以更好的保证数据安全性。

4). AOF包含一个格式清晰、易于理解的日志文件用于记录所有的修改操作。事实上,我们也可以通过该文件完成数据的重建。

AOF的劣势有哪些呢?

1). 对于相同数量的数据集而言,AOF文件通常要大于RDB文件。RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快。

2). 根据同步策略的不同,AOF在运行效率上往往会慢于RDB。总之,每秒同步策略的效率是比较高的,同步禁用策略的效率和RDB一样高效。

详解参考:https://www.cnblogs.com/shizhengwen/p/9283973.html

4.什么是缓存和数据库双写不一致?怎么解决?

解释:连续写数据库和缓存,但是操作期间,出现并发了,数据不一致了。
通常,更新缓存和数据库有以下几种顺序:

 1. 先更新数据库,再更新缓存。
    
 2. 先删缓存,再更新数据库。
    
 3. 更新数据库,再删除缓存。

1.先更新数据库,再更新缓存。

这么做的问题是:当有 2 个请求同时更新数据,那么如果不使用分布式锁,将无法控制最后缓存的值到底是多少。也就是并发写的时候有问题。

2.先删缓存,再更新数据库。

该方案会导致不一致的原因是。同时有一个请求A进行更新操作,另一个请求B进行查询操作。那么会出现如下情形:

1)请求A进行写操作,删除缓存
2)请求B查询发现缓存不存在
3)请求B去数据库查询得到旧值
4)请求B将旧值写入缓存
5)请求A将新值写入数据库
上述情况就会导致不一致的情形出现。而且,如果不采用给缓存设置过期时间策略,该数据永远都是脏数据。

解决方案:采用延时双删策略

3.先更新数据库,再删除缓存。

假设这会有两个请求,一个请求A做查询操作,一个请求B做更新操作,那么会有如下情形产生:

1)缓存刚好失效
2)请求A查询数据库,得一个旧值
3)请求B将新值写入数据库
4)请求B删除缓存
5)请求A将查到的旧值写入缓存
ok,如果发生上述情况,确实是会发生脏数据

发生上述情况有一个先天性条件,就是步骤(3)的写数据库操作比步骤(2)的读数据库操作耗时更短,才有可能使得步骤(4)先于步骤(5)。
可是,大家想想,数据库的读操作的速度远快于写操作的(不然做读写分离干嘛,做读写分离的意义就是因为读操作比较快,耗资源少),因此步骤(3)耗时比步骤(2)更短,这一情形很难出现。

优化方案:

方案一

先做一个说明,从理论上来说,给缓存设置过期时间,是保证最终一致性的解决方案。
这种方案下,我们可以对存入缓存的数据设置过期时间,所有的写操作以数据库为准,对缓存操作只是尽最大努力即可。
也就是说如果数据库写成功,缓存更新失败,那么只要到达过期时间,则后面的读请求自然会从数据库中读取新值然后回填缓存。

方案二

Redis里的数据总是不过期,但是有个背景更新任务(“定时执行的代码” 或者 “被队列驱动的代码)读取db,把最新的数据塞给Redis。
这种做法将Redis看作是“存储”。访问者不知道背后的实际数据源,只知道Redis是唯一可以取的数据的地方。
当实际数据源更新时,背景更新任务来将数据更新到Redis。
这时还是会存在Redis和实际数据源不一致的问题。
如果是定时任务,最长的不一致时长就是更新任务的执行间隔;
如果是用类似于队列的方式来更新,那么不一致时间取决于队列产生和消费的延迟。
常用的队列(或等价物)有Redis(怎么还是Redis),Kafka,AMQ,RMQ,binglog,log文件,阿里的canal等。

5.sql优化步骤

sql优化一般步骤概要:

 通过 show status 命令了解各种sql的执行频率
 定位执行效率较低的sql语句
 通过explain分析低效sql的执行计划
 通过 show profile 分析sql
 通过trace分析 优化器 如何选择执行计划
 确定问题并采取相应的优化措施

6.索引中B+树和hash树区别

Hash索引结构的特殊性,其检索效率非常高,索引的检索可以一次定位,不像B-Tree索引需要从根节点到枝节点,最后才能访问到页节点这样多次的IO访问,所以Hash 索引的查询效率要远高于 B-Tree索引。 既然Hash 索引的效率要比 B-Tree 高很多,为什么大家不都用 Hash索引而还要使用 B-Tree索引呢?任何事物都是有两面性的,Hash 索引也一样,虽然 Hash 索引效率高,但是 Hash索引本身由于其特殊性也带来了很多限制和弊端。
Hash索引仅仅能满足"=",“IN"和”<=>"查询,不能使用范围查询。
Hash 索引无法被用来避免数据的排序操作。
Hash索引不能利用部分索引键查询。
Hash索引在任何时候都不能避免表扫描。
Hash索引遇到大量Hash值相等的情况后性能并不一定就会比B-Tree索引高。

7.事务隔离级别

在这里插入图片描述

8.Linux查看日志命令

less
more 
cat 
tail 
实时查看日志输出:tail -f 
查看日志末尾100行: tail -n 100

9.top命令

参考:https://www.cnblogs.com/niuben/p/12017242.html
在这里插入图片描述

第一行:任务队列信息,同 uptime 命令的执行结果
第二行:Tasks — 任务(进程):总进程:150 total, 运行:1 running, 休眠:149 sleeping, 停止: 0 stopped, 僵尸进程: 0 zombie
第三行:cpu状态信息
第四行:内存状态
第五行:swap交换分区信息
第六行:空行
第七行:各进程(任务)的状态监控
PID — 进程id
USER — 进程所有者
PR — 进程优先级
NI — nice值。负值表示高优先级,正值表示低优先级
VIRT — 进程使用的虚拟内存总量,单位kb。VIRT=SWAP+RES
RES — 进程使用的、未被换出的物理内存大小,单位kb。RES=CODE+DATA
SHR — 共享内存大小,单位kb
S —进程状态。D=不可中断的睡眠状态 R=运行 S=睡眠 T=跟踪/停止 Z=僵尸进程
%CPU — 上次更新到现在的CPU时间占用百分比
%MEM — 进程使用的物理内存百分比
TIME+ — 进程使用的CPU时间总计,单位1/100秒
COMMAND — 进程名称(命令名/命令行)

10.线程池创建方式

线程池的创建方式一共包含以下7种(其中六种是通过Executors创建的,一种是通过ThreadPoolExecutor创建的):
1.Executors.newFixedThreadPool:创建一个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待;
2.Executors.newCachedThreadPool:创建一个可缓存的线程池,若线程数超过处理所需,缓存一段时间后会回收,若线程数不够,则新建线程;
3.Executors.newSingleThreadExecutor:创建单个线程数的线程池,它可以保证先进先出的执行顺序;
4.Executors.newScheduledThreadPool:创建一个可以执行延迟任务的线程池;
5.Executors.newSingleThreadScheduledExecutor:创建一个单线程的可以执行延迟任务的线程池;
6.Executors.newWorkStealingPool:创建一个抢占式执行的线程池(任务执行顺序不确定)【JDK 1.8 添加】。
7.ThreadPoolExecutor:最原始的创建线程池的方式,它包含了 7 个参数可供设置,(corePoolSize,maximumPoolSize,keepAliveTime,unit,workQueue,threadFactory,handler)
ThreadPoolExecutor参数详解:
1. corePoolSize 线程池核心线程大小
  线程池中会维护一个最小的线程数量,即使这些线程处理空闲状态,他们也不会被销毁,除非设置了allowCoreThreadTimeOut。这里的最小线程数量即是corePoolSize。
  
2. maximumPoolSize 线程池最大线程数量
 一个任务被提交到线程池以后,首先会找有没有空闲存活线程,如果有则直接将任务交给这个空闲线程来执行,如果没有则会缓存到工作队列(后面会介绍)中,如果工作队列满了,才会创建一个新线程,然后从工作队列的头部取出一个任务交由新线程来处理,而将刚提交的任务放入工作队列尾部。线程池不会无限制的去创建新线程,它会有一个最大线程数量的限制,这个数量即由maximunPoolSize指定。
 
3. keepAliveTime 空闲线程存活时间
 一个线程如果处于空闲状态,并且当前的线程数量大于corePoolSize,那么在指定时间后,这个空闲线程会被销毁,这里的指定时间由keepAliveTime来设定。
 
4. unit 空闲线程存活时间单位
 keepAliveTime的计量单位,当线程池中线程数大于核心线程数时,线程的空闲时间如果超过线程存活时间,那么这个线程就会被销毁,直到线程池中的线程数小于等于核心线程数。
 
5. workQueue 工作队列
 ①ArrayBlockingQueue
 基于数组的有界阻塞队列,按FIFO排序。新任务进来后,会放到该队列的队尾,有界的数组可以防止资源耗尽问题。当线程池中线程数量达到corePoolSize后,再有新任务进来,则会将任务放入该队列的队尾,等待被调度。如果队列已经是满的,则创建一个新线程,如果线程数量已经达到maxPoolSize,则会执行拒绝策略。
 ②LinkedBlockingQuene
 基于链表的无界阻塞队列(其实最大容量为Interger.MAX),按照FIFO排序。由于该队列的近似无界性,当线程池中线程数量达到corePoolSize后,再有新任务进来,会一直存入该队列,而不会去创建新线程直到maxPoolSize,因此使用该工作队列时,参数maxPoolSize其实是不起作用的。
 ③SynchronousQuene
 一个不缓存任务的阻塞队列,生产者放入一个任务必须等到消费者取出这个任务。也就是说新任务进来时,不会缓存,而是直接被调度执行该任务,如果没有可用线程,则创建新线程,如果线程数量达到maxPoolSize,则执行拒绝策略。
 ④PriorityBlockingQueue
 具有优先级的无界阻塞队列,优先级通过参数Comparator实现。

6.threadFactory(线程工厂)
 用于创建新线程。threadFactory创建的线程也是采用new Thread()方式,threadFactory创建的线程名都具有统一的风格:pool-m-thread-n(m为线程池的编号,n为线程池内的线程编号)。

7.handler 拒绝策略
 当工作队列中的任务已到达最大限制,并且线程池中的线程数量也达到最大限制,这时如果有新任务提交进来,该如何处理呢。这里的拒绝策略,就是解决这个问题的,jdk中提供了4中拒绝策略:
 ①CallerRunsPolicy
 该策略下,在调用者线程中直接执行被拒绝任务的run方法,除非线程池已经shutdown,则直接抛弃任务。
 ②AbortPolicy
 该策略下,直接丢弃任务,并抛出RejectedExecutionException异常。
 ③DiscardPolicy
 该策略下,直接丢弃任务,什么都不做。
 ④DiscardOldestPolicy
 该策略下,抛弃进入队列最早的那个任务,然后尝试把这次拒绝的任务放入队列。

答主是个废物

好记性不如烂笔头

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值