一、常用指令
哈希Hash kv模式不变,但v是一个键值对
(1)hset、hget命令用于为哈希表中的字段赋值 。
(2)hmset、hmget 同时将多个field-value对设置到哈希表中。会覆盖哈希表中已存在的字段。
(3)hgetall 用于返回哈希表中,所有的字段和值。
(4)hdel 用于删除哈希表 key 中的一个或多个指定字段
(5)hlen 获取哈希表中字段的数量
(6)hexists 查看哈希表的指定字段是否存在。
(7)hkeys 获取哈希表中的所有域(field)。
(8)hvals 返回哈希表所有域(field)的值。
(9)hincrby 为哈希表中的字段值加上指定增量值。
(10)hsetnx 为哈希表中不存在的的字段赋值
有序集合Zset
在set基础上,加一个score值。之前set是k1 v1 v2 v3,现在zset是 k1 score1 v1 score2 v2
(1)zadd 将一个或多个成员元素及其分数值加入到有序集当中。
(2)zrange 返回有序集中,指定区间内的成员
(3)zrangebyscore 返回有序集合中指定分数区间的成员列表。有序集成员按分数值递增(从小到大)次序排列。
(4)zrem 移除有序集中的一个或多个成员
(5)zcard 命令用于计算集合中元素的数量
(6)zcount 计算有序集合中指定分数区间的成员数量
(7)zrank 返回有序集中指定成员的排名。其中有序集成员按分数值递增(从小到大)顺序排列。
(8)zrevrank 返回有序集中成员的排名。其中有序集成员按分数值递减(从大到小)排序。
和set相比,sorted set增加了一个权重参数score,使得集合中的元素能够按score进行有序排列,比如一个存储全班同学成绩的sorted set,其集合value可以是同学的学号,而score就可以是其考试得分,这样在数据插入集合的时候,就已经进行了天然的排序。可以用sorted set来做带权重的队列,比如普通消息的score为 1 ,重要消息的score为 2 ,然后工作线程可以选择按score的倒序来获取工作任务。让重要的任务优先执行。
二、Redis事务
1.概念
redis事务就是一次性、顺序性、排他性的执行一个队列中的一系列命令。 Redis事务没有隔离级别的概念
Redis不保证原子性
Redis中,单条命令是原子性执行的,但事务不保证原子性,且没有回滚。事务中任意命令执行失败,其余的命令仍会被执行。
Redis事务的三个阶段:
(1)开始事务
(2)命令入队
(3)执行事务
2.Redis事务相关命令
(1)multi # 标记一个事务块的开始( queued )
(2)exec # 执行所有事务块的命令 ( 一旦执行exec后,之前加的监控锁都会被取消掉 )
(3)discard # 取消事务,放弃事务块中的所有命令
若在事务队列中存在命令性错误(类似于java编译性错误),则执行EXEC命令时,所有命令都不会执行
若在事务队列中存在运行时性错误(类似于java的1/0的运行时异常),则执行EXEC命令时,其他正确命令会被执行,错误命令抛出异常。
(4)Watch 监控
watch key1 key2... #监视一或多个key,如果在事务执行之前,被监视的key被其他命令改动,则事务被打断 ( 类似乐观锁 )
watch指令类似于乐观锁,在事务提交时,如果watch监控的多个KEY中任何KEY的值已经被其他客户端更改,则使用EXEC执行事务时,事务队列将不会被执行,同时返回Nullmulti-bulk应答以通知调用者事务执行失败。
三、持久化
1.RDB快照形式
在指定的时间间隔内将内存中的数据集快照写入磁盘,它恢复时是将快照文件直接读到内存里
(1)Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到 一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。 整个过程中,主进程是不进行任何IO操作的,这就确保了极高的性能。如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。
(2)RDB的缺点是最后一次持久化后的数据可能丢失。
(3)rdb 保存的是dump.rdb文件
如何触发RDB快照
2.AOF日志形式
以日志的形式来记录每个写操作,将Redis执行过的所有写指令记录下来(读操作不记录), 只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis 重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作
优势:每修改同步:appendfsync always 同步持久化 每次发生数据变更会被立即记录到磁盘 性能较差但数据完整性比较好
劣势:相同数据集的数据而言aof文件要远大于rdb文件,恢复速度慢于rdb
四、缓存穿透
1.概念
用户想要查询一个数据,发现redis内存数据库没有,也就是缓存没有命中,于是向持久层数据库查询。发现也没有,于是本次查询失败。当用户很多的时候,缓存都没有命中,于是都去请求了持久层数据库。这会给持久层数据库造成很大的压力,这时候就相当于出现了缓存穿透。
2.解决方案
缓存空对象
当存储层不命中后,即使返回的空对象也将其缓存起来,同时会设置一个过期时间,之后再访问这个数据将会从缓存中获取,保护了后端数据源;
3.存在问题
(1)如果空值能够被缓存起来,这就意味着缓存需要更多的空间存储更多的键,因为这当中可能会有很多的空值的键;
(2)即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一致,这对于需要保持一致性的业务会有影响。
五、缓存击穿
1.概念:
当某个key在过期的瞬间,有大量的请求并发访问,这类数据一般是热点数据,由于缓存过期,会同时访问数据库来查询最新数据,并且回写缓存,会导使数据库瞬间压力过大。
2.解决方案
(1)设置热点数据永不过期
从缓存层面来看,没有设置过期时间,所以不会出现热点 key 过期后产生的问题。
(2)加互斥锁
分布式锁:使用分布式锁,保证对于每个key同时只有一个线程去查询后端服务,其他线程没有获得分布式锁的权限,因此只需要等待即可。这种方式将高并发的压力转移到了分布式锁,因此对分布式锁的考验很大。
六、缓存雪崩
1.概念
缓存雪崩,是指在某一个时间段,缓存集中过期失效。
因为自然形成的缓存雪崩,一定是在某个时间段集中创建缓存,这个时候,数据库也是可以顶住压力的。无非就是对数据库产生周期性的压力而已。而缓存服务节点的宕机,对数据库服务器造成的压力是不可预知的,很有可能瞬间就把数据库压垮。
2.解决方案
(1)redis高可用
这个思想的含义是,既然redis有可能挂掉,那我多增设几台redis,这样一台挂掉之后其他的还可以继续工作,其实就是搭建的集群。
(2)限流降级
这个解决方案的思想是,在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
(3)数据预热
数据加热的含义就是在正式部署之前,我先把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中。在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。
七、GC
JVM内存区域
方法区
是被所有线程共享的内存区域,用来存储已被虚拟机加载的类信息、常量、静态变量、JIT(just in time,即时编译技术)编译后的代码等数据。运行时常量池是方法区的一部分,用于存放编译期间生成的各种字面常量和符号引用。
堆
线程共享。所有的对象实例以及数组都要在堆上分配。回收器主要管理的对象。
它的目的是存放对象实例。同时它也是GC所管理的主要区域,因此常被称为GC堆,又由于现在收集器常使用分代算法,Java堆中还可以细分为新生代和老年代
程序计数器
虚拟机栈
本地方法栈(JVM执行本地方法)
判断垃圾/判断对象已死
1.引用计数算法
引用计数算法是在对象中加入一个计数器,当对象被引用,计数器+1,当引用失效,计数器-1,当计数器的值编程0,就是没有任何一个变量来引用这个对象,那么这个对象就是垃圾
会引起内存泄漏
2.可达性分析算法(Java使用的这一种)
Java中定义了一些起始点,称为GC Root[正在使用的对象或量],当有对象引用它的时候,就把对象挂载在它下面,形成一个树状结构,当一个对象处于一个这样的树里时,就认为此对象是可达的,反之是不可达
GC ROOT
虚拟机栈中引用的对象
方法区类的静态成员引用的对象
方法区常量引用的对象
本地方法栈中JNI(Java Native Interface的缩写)引用的对象
垃圾收集算法
(1)标记清除(Mark-Sweep)
算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象(引用计数法或者可达性分析),在标记完成后统一回收掉所有被标记的对象。它是最基础的收集算法,后续的收集算法都是基于这种思路并对其缺点进行改进而得到的。
它的主要缺点有两个:一个是效率问题,标记和清除过程的效率都不高;另外一个是空间问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致,当程序在以后的运行过程中需要分配较大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。
(2)标记复制(Copying)
复制算法在标记清除算法的基础上,针对内存碎片问题做了一下优化,此算法把内存分为大小相同的两块,每次在使用的时候只使用其中的一块。当一块内存用完的时候。把存活对象复制到另外的一块中,然后清除当前这块中的所有的对象,如此反复。
解决了内存碎片化严重的问题,但是存在缺陷就是每次只使用一半的空间,空间利用率受到影响。同时对于存活周期长的对象,复制次数多。
(3)标记整理(Mark-Compact)
标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存
(4)分代收集算法(Generational Collection)
把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记-清理”或“标记-整理”算法来进行回收。
新生代GC叫做 minorGC 伊甸园区满了
老年代GC叫做FullGC 老年代满了会触发fullGC fullGC老年代和新生代一起GC