redis
redis 典型的 nosql 数据库,key-value
nosql (not only sql):非关系型数据库。nosql不依赖于业务逻辑方式存储,而以简单的key-value模式存储。因此大大的增加了数据库的扩展能力。
特点:1.不遵循SQL标准
2.不支持ACID
3.远超与sql的性能
nosql的使用场景:1.对数据高并发的读写。2.海量数据的读写。3.对数据高扩展性的
nosql的不适用场景:1.需要实物支持。2.基于sql的结构化查询存储
常见的nosql数据库:redis mongoDB
redis安装成功之后
redis中默认16个数据库。初始默认使用0号库。
dbsize :查看当前数据库key的数量
flushdb 清空当前库
flushall 通杀全部库
select 转换库
debug reload 服务器运行过程中重启
shutdown save :关闭服务器时保存
redis 登陆方式1:redis-service 将日志信息直接打印到控制台
登陆方式2:nohup redis-service redis-conf &
redis 换端口号登录: redis-service --port 端口号
启动时: redis-cli -p 端口号
redis 键 (key)
redis 字符串 (String)
String 是redis中最基本的数据类型,一个redis中字符串value最多可以是512M,String类型是二进制安全的,可以包含任何数据。比如jpg图片或者序列化的对象。
redis原子性:原子操作是指不会被线程调度机制打断的操作
1.java中i++是否为原子操作 不是 2.i=0;两个线程分别对i进行++100次,值是多少? 2-200
redis String的数据结构:简单的动态字符串
redis列表(List)
单键多值
redis列表是简单的字符串列表,底层是双向列表。从左边/右边插入值
redis中list数据结构:快速列表(quicklist)
数据比较少的情况下:还用一块连续的内存存储,这个结构是ziplist(压缩列表)
数据比较多的情况下:使用quicklist
redis集合(set)
redis中set是String类型的无序集合,底层是一个 value为null的hash表。
redis set数据结构:字典。字典是用哈希表实现的。
redis 哈希(hash)
redis hash 是一个键值对集合。
redis hash 是一个String类型的Field和value的映射表,hash适合于存储对象,类似于java中Map<String,Object>
hash数据结构两种:
数量少,长度短:ziplist;否则使用hashtable。
redis有序集合zset(sorted set)
redis有序集合是一个没有重复元素的字符串集合。与普通集合set不同的是,有序集合中的每个成员都关联了一个评分(score),集合中的成员是唯一的,但评分是可以重复的。
zset数据结构(底层使用了两个数据结构):
1.hash:hash的作用就是关联元素value和权重score,保障元素value的唯一性,可以通过元素value找到score值。
2.跳跃表:目的在于给元素value排序,根据score的范围获取数据。
redis 6中的发布与订阅
是一种消息通信模式。
redis 新数据类型
BitMaps:本身不是数据类型,实际上就是字符串(key-value),它可以对字符串的位进行运算。可以把BitMaps想象为只存0或1的数组,数组的下标在BitMaps中叫做偏移量。
在第一次初始化BitMaps时,假如偏移量非常大,那么整个初始化过程会比较慢,可能会造成Redis阻塞。
BITPOS:返回指定值0或1在指定区间第一次出现的位置。
bitop 【and/or】 【存储key】 【key1】 【key2】
hyperloglog:做基数统计(基数:不重复数值的个数)
redis中jedis的使用(用java代码操作redis)
@Test public void testJedis(){ //连接redis Jedis jedis = new Jedis("192.168.11.128", 6379); //操作redis /*操作string jedis.set("name","hyy"); String name = jedis.get("name"); System.out.println(name); */ //操作list jedis.lpush("list1","a","b","c"); jedis.rpush("list1","x","y","z"); List<String> list1 = jedis.lrange("list1", 0, -1); for (String s : list1) { System.out.println(s); } //关闭连接 jedis.close(); } @Test public void testJedis1(){ //连接redis Jedis jedis = new Jedis("192.168.11.128", 6379); //操作redis jedis.hset("hash1","a1","a1"); jedis.hset("hash1","a2","a2"); jedis.hset("hash1","a3","a3"); Map<String, String> hash1 = jedis.hgetAll("hash1"); System.out.println(hash1); //关闭连接 jedis.close(); }
jedis案例:模拟验证码登录
package com.gym; import org.junit.Test; import org.w3c.dom.ranges.Range; import redis.clients.jedis.Jedis; import java.util.Random; /** * @author 郭玉敏 * @create 2021-11-14-13:03 * @create 明天吃烤肉 */ public class PhoneCode { public static void main(String[] args) { verifyCode("19155977383"); getRedisCode("19155977383","775461"); } //验证码校验 public static void getRedisCode(String phone,String code){ //从redis中获取验证码 Jedis jedis = new Jedis("192.168.11.128",6379); String codeKey = "VeriftCode"+phone+":code"; String s = jedis.get(codeKey); //判断 if (s.equals(code)){ System.out.println("成功"); }else{ System.out.println("field"); } } //2.每个手机只能发送三次,验证码发到redis中,设置过期时间 public static void verifyCode(String phone){ //连接redis Jedis jedis = new Jedis("192.168.11.128",6379); //拼接key //手机发送次数 String countKey = "VeriftCode"+phone+":count"; //验证码key String codeKey = "VeriftCode"+phone+":code"; //每个手机只能发送三次 String count = jedis.get(countKey); if (count == null){ //没有发送次数,第一次发送 //设置发送次数为1 jedis.setex(countKey,24*60*60,"1"); }else if (Integer.parseInt(count) <= 2){ jedis.incr(countKey); }else if (Integer.parseInt(count) > 2){ System.out.println("发送次数超过三次"); jedis.close(); return; } //发送的验证码放到redis里面去 String vcode = getCode(); //过去时间120秒 jedis.setex(codeKey,120,vcode); jedis.close(); } //1.生成6位数字验证码 public static String getCode(){ Random random = new Random(); String code=""; for (int i=0;i<6;i++){ int rand = random.nextInt(10); code += rand; } return code; } }检验 验证码
redis持久化
redis持久化:利用永久性存储介质将数据进行保存,在出现问题时,进行数据恢复。
redis持久化的意义:保证数据安全,防止数据丢失。
持久化方案:
RDB启动方式——save指令(手动执行一次保存操作)
save指令的执行会阻塞当前redis服务器,直到当前RDB过程完成为止,可能会造成较长时间的阻塞。
bgsave指令:在后台执行保存操作。
自动执行保存: save second changes
second:监控时间范围内
changes:监控key的变化量
在conf文件中进行设置
RDB三种启动方式对比:
RDB优点:
AOF
RDB存储的弊端:
AOF:以独立日志的方式记录每次写命令,重启时再重新执行AOF文件中命令达到恢复数据的目的。AOF相较于RDB就是 改记录数据为记录数据产生的过程。
AOF写数据的三种方式:
AOF工作原理:
事物
redis执行指令过程中,多条连续执行的指令被干扰,打断,插队。
redis事物:一个队列中,一次性、顺序性、排他性的执行一系列的命令。
事物的基本操作:
1.开启事物:multi
设置事物的开启位置,执行此命令后,后续的所有指令均加入到事物中
2.执行事物: exec
设定事物的结束位置,同时执行事物,与multi成对出现,成对使用。
加入事物中的命令并不会马上执行,而是等exec之后才执行。
3.取消事物:discard
终止事物的进程,发生位置在multi~exec
基于特定条件的事物执行——锁
1.对key添加监视锁:watch key1【key...】
2.取消对所有key的监视:unwatch
注意:必须要在事物开始之前进行watch,如果watch的内容发生了改变,watch后面事物就不会执行了。
分布式锁
使用setnx设置一个公共锁:setnx lock-key value
1.有值返回设置失败,不拥有控制权,排队或等待
2.设置成功的,拥有控制权,进行下一步具体业务操作。
通过 del 释放分布式锁
防止忘记关锁/redis宕机
expire lock-key second
pexpire lock-key milliseconds
锁的时间不要设置过长,锁时间设定推荐:最大耗时*120%+平均网络延迟*110%
删除策略
删除策略:
过期数据:
数据删除策略:定时删除、惰性删除、定期删除
定时删除:用时间换空间
优点:节约内存,到时就删除
缺点:CPU压力大,无论现在CPU此时的负载高不高,均会占用cpu。
惰性删除:(用空间换时间)数据到达过期时间,不做处理。等下次访问数据时:发现未过期,返回数据。
发现已过期,删除,返回不存在
优点:节约cpu性能,发现必须删除时才删除。
缺点:内存压力大,出现长期占用内存的数据。
定期删除:redis启动服务器初始化时,读取配置server.hz,默认为10
每秒钟执行server.hz次serverCron() --> databasesCron()【一个一个检查完】 -->activeExpireCycle()【检查任意一个】
定期删除:周期性抽查存储空间
逐出算法
当数据进入redis中,内存不够了怎么办?(不包含过期数据导致的内存不足)
redis使用内存存储数据时,在执行每一个命令之前,会调用freeMemorylfNeeded 检测内存是否充足,如果不满足新加入数据的最低存储要求,redis要临时删除一些数据为当前指令清理存储空间。清理数据的策略称为逐出算法。
逐出算法不是100%清理出足够的可使用空间,如果不成功则反复执行。当对所有数据尝试完毕后,如果不能达到内存清理的要求,将出现错误信息。
最大可使用内存:maxmemory
每次选取删除数据的个数:maxmemory-samples
选取数据时并不会全库扫描,导致严重的性能消耗,降低读写性能。
删除策略:maxmemory-policy
达到最大内存后,对被挑选出来的数据进行删除的策略。
修改redis.conf 核心配置文件
面试(企业级解决方案)
缓存预热:
缓存雪崩:就是瞬间数据量过大,导致对数据库服务器造成压力。
缓存击穿:就是单个高热数据过期的瞬间,数据访问量较大,未命中redis后,发起了大量对同一数据的数据库访问,导致对数据库服务器造成压力。应用策略应该在业务数据分析与预防方面进行,配合运行监控测试与即时调整策略,毕竟单个key的过期监控难度较高,配合雪崩处理策略即可。
缓存穿透:缓存击穿访问了不存在的数据,调过来合法数据的redis数据缓存阶段,每次访问数据库,导致对数据库服务器造成压力,
性能指标监控