总结
1>redis 概念 优缺点 定位 与mysql的区别(必须会)2>redis 常用数据类型命令
-
强调加粗命令必须掌握
-
当前阶段至少-->指定redis有哪些功能
3>redis 其他全局命令4>redis 事务操作(能讲出来)5>redis 持久化机制策略(能讲出来)6>redis key淘汰机制 redis过期key策略(能讲出来)7>java 操作redis
-
jedis
-
SpringBoot-redis
复习
mybatis-plusspringbootjs
vue 前后端分离
---------------------------------------------------------------------------------------------------------------------------------
拓展
小案例--文章阅读数统计
---------------------------------------------------------------------------------------------------------------------------------
一 入门
1>简介
键值对形式的非关系型数据库
-
是以key-value形式存储,和传统的关系型数据库不一样.不一定遵循传统数据库的一些基本要求.(非关系型的,分布式的,开源的,水平可拓展的)
-
redis定位是缓存, 提高数据读写速度, 减轻对数据库存储与访问压力(记住)
-
项目中涉及到缓存-->优先考虑方案-->redis
问题-->能不能将重要数据放到缓存(临时存储)
回答-->不能,因为不能保证数据安全保存
优点
-
对数据高并发读写(直接是内存中进行读写的)
-
对海量数据的高效率存储和访问
-
对数据的可拓展性和高可用性
-
单线程操作,每个操作都是原子操作,没有并发相关问题(redis 6)
缺点
-
redis(ACID处理非常简单)
-
无法做太复杂的关系数据库模型
2>数据库分类
-
关系型数据库
-
NoSql数据库
-
非关系型数据库特点
-
与关系型数据区分
二 数据类型
命令格式类型命令 key 参数数据set name gutianle
1>String类型
Map<String,String>mapmap.put("name", Json.toJsonString(user));可以包含任何数据 比如图片序列化的对象,最大容量为512mb
必须掌握的指令
set key value -> 存入键值对
get key -> 根据键取出值
incr key --> 把值递增1
decr key--> 把值递减1
del key -> 根据键删除键值对
setex key timeout value -> 存入键值对
timeout表示失效时间,单位s从你开始写setex开始计算时间
ttl key->可以查询出当前的key还剩余多长时间过期
setnx key value -> 如果key已经存在,
不做操作, 如果key不存在,直接添加 分布式锁的一种实现方案
应用场景
1)计数器许多运用都会使用redis作为计数的基础工具,他可以实现快速计数、查询缓存的功能,同时数据可以异步落地到其他的数据源
如:视频播放数系统就是使用redis作为视频播放数计数的基础组件
2)共享session
-->当应用进行分布式集群部署的时候,如何保证不同服务器上session信息能够共享呢??
-->出于负载均衡的考虑,分布式服务会将用户信息的访问均衡到不同服务器上,用户刷新一次访问可能会需要重新登录,为避免这个问题可以用redis将用户session集中管理,在这种模式下只要保证redis的高可用和扩展性的,每次获取用户更新或查询登录信息都直接从redis中集中获取
回顾知识-->Session的实现原理:
-
服务端首先查找对应的cookie的值(sessionid)。
-
根据sessionid,从服务器端session存储中获取对应id的session数据,进行返回。
-
如果找不到sessionid,服务器端就创建session,生成sessionid对应的cookie,写入到响应头中
基于session集中存储的实现方案:
-
新增Filter,拦截请求,包装HttpServletRequest
-
改写getSession方法,从session存储中获取session数据,返回自定义的HttpSession实现
-
在生成新Session后,写入sessionid到cookie中
2>Hash类型(无序)
Hash类型是String类型的field和value的映射表.或者说是一个String集合.它特别适合存储对象,相比较而言,讲一个对象存储在Hash类型里要比存储在String类型里占用更少的内存空间,并方便存储整个对象
必须掌握的命令
hset key hashkey hashvalue -> 存入一个hash对象
hget key hashkey -> 根据hash对象键取去值
hexists key hashkey -> 判断hash对象是含有某个键
hdel key hashkey -> 根据hashkey删除hash对象键值对
其他操作
应用场景
-->缓存用户信息
String和hash两种方案的选择
-->看需求的侧重点
3>list类型(有序)
类似java中的queue-->Map<String, List>
必须掌握的指令
rpush key value -> 往列表右边添加数据
lrange key start end -> 范围显示列表数据,全显示则设置0 -1
0代表开始 -1代表最后一个
lpush key value -> 往列表左边添加数据
lpop key -> 弹出列表最左边的数据
rpop key -> 弹出列表最右边的数据
llen key -> 获取列表长度
应用场景
用户收藏文章列表
用户:[文章id1,文章id2....]
4>set类型(无序唯一)
和list的区别
-
有序无序
-
是否可重复
必须掌握的指令
sadd key value -> 往set集合中添加元素
smembers key -> 列出set集合中的元素
srem key value -> 删除set集合中的元素
spop key count -> 随机弹出集合中的元素(删除)
补充指令
sdiff key1 key2 -> 返回key1中特有元素(差集)
(a b c d)-(a d e f)-->b c
(a d e f)-(a b c d)-->e f
(a b c d)-(a d e f b h)-->c
sinter key1 key2 -> 返回两个set集合的交集-->去重
(a b c d)-(a d e f b h)-->d a b
sunion key1 key2 -> 返回两个set集合的并集
(a b c d)-(a d e f b h)-->a b c d e f h
应用场景
1)去重
2)抽奖
1,准备一个抽奖池:sadd luckydraw 1 2 3 4 5 6 7 8 9 10 11 12 13
2,抽3个三等奖:spop luckydraw 3
3,抽2个二等奖:spop luckydraw 2
4,抽1个二等奖:spop luckydraw 1
5>sorted_set类型
必须掌握的指令
zadd key score column -> 存入分数和名称
zincrby key score column -> 偏移名称对应的分数
zrange key start end -> 按照分数升序输出名称
zrevrange key start end -> 按照分数降序输出名称
zrank key name -> 升序返回排名
zrevrank key name -> 降序返回排名
zcard key -> 返回元素个数
应用场景
排行榜
三 redis的选用
要不要用缓存-->优先考虑redis-->本质上是map集合-->分析k咋样 v咋样-->如何设计kv
value值的设计
1>第一种(必须掌握)
2>第二种
如果后续用java操作redis 里面所有的kv从redis获取出来的数据都是Object类型,操作起来就很麻烦,还得转换回来字符串有些公司为了减少转换过程,所以全部转换成字符串,拿出来以后是json格式的字符串,再用json的工具换成能用的对象
key值的设计
1>唯一性
-->怎么保证key值的唯一性-->一般数据表主键
2>可读性
-->加上可读性前缀-->项目名+ 业务模块 + 可识别的前缀 +唯一性标识
3>灵活性
-->项目二再说
4>时效性
-->需要考虑key是否为临时还是永久
四 redis的进阶
高级命令
-->可以模糊查询
1>exists
2>expire
和setex的区别???设置的时候 创建的时候
3>persist
4>flushdb
清空当前库
select 选择数据库 数据库为0到15(一共16个数据库) 默认进入的是0数据库
move key 讲当前数据中的key转移到其他数据库中
randomkey 随机返回数据库里的一个key
rename重命名key
echo 打印名
dbsize 查看数据库的key数量
info 获取数据库信息
config get 实时传储收到的请求(返回相关的配置信息)
config get * 返回所有配置
安全性设置
-->搜索关键词-->requirepass-->443行 去掉#修改密码admin-->登陆的时候(正常操作)
事务(高频面试题)
单个redis命令的执行时原子性的,但redis没有在事务上增加任何维持原子性的机制,所以redis事务的执行并不是原子性支持事务-->事务可以理解为一个打包的批量执行脚本,但批量指令并非原子化操作,中间某条指令的失败不会导致前面已做指令的回滚,也不会造成后续的指令不做不支持事务-->对应mysql事务的概念就是不支持事务(一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节)
从开始到执行会经历三个阶段
-
开始事务(MULTI)
-
命令入队
-
执行事务
自己描述-->Redis事务可以理解为一个打包的批量执行脚本,然后一次性、按顺序地执行多个命令的机制.且批量指令并非原子化操作,中间某条指令的失败不会导致前面已做指令的回滚,也不会造成后续的指令不做.(这个也是和mysql事务原子性特性的区别)redis事务从开始到执行会经历三个阶段1>开始事务(MULTI) 2>命令入队 3>执行事务
事务持久化机制(高频面试题)
-->会将内存的数据按照某种逻辑写到硬盘中
1>RDB方式
snapshotting(快照)默认方式.将内存中以快照的方式写入到二进制文件中.默认为dump.rdb.可以配置设置自动做快照持久化方式.我们可以配置redis在n秒内如果超过m个key就修改自动做快照
缺点-->两次快照之间数据可能会丢失
2>AOF方式
append-only file (缩写aof)的方式,由于快照方式是在一定时间间隔做一次,所以可能发生reids意外down的情况就会丢失最后一次快照后的所有修改的数据.aof比快照方式有更好的持久化性,是由于在使用aof时,redis会将每一个收到的写命令都通过write函数追加到命令中,当redis重新启动时会重新执行文件中保存的写命令来在内存中重建这个数据库的内容.这个文件在bin目录下:appendonly.aof,aof不是立即写到硬盘中,可以通过配置文件修改强制写到硬盘中
设置-->appendonly yes //启动aof持久化方式有三种修改方式
你怎么考虑的,这两个redis的方式
1>数据的重要性 提高备份的时间/频率
2>数据的数量 快照不适合 用AOF
内存淘汰机制和过期key的处理(高频面试题)
分为四大类
1>LRU
最近最少使用,仅与时间有关
2>LFU
最不经常使用,与频次/时间有关
3>TTL
设置了过期时间的,还没到时间,会提前淘汰掉
4>随机淘汰
全凭天意-->通过maxmemroy-policy可以配置具体的淘汰机制
完整的
五 实际运用
关注一下编程语言排行榜 tiobe
jedis中的基本运用
jedis中的方法就是redis的命令
1>导进springboot的依赖(也可以不用)
导进redis相关依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.3</version>
<relativePath/>
</parent>
<dependencies>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
</dependencies>
2>在测试包新建测试类
3>写一个普通main方法
1)v1版本(默认连接池)
public static void main(String[] args) {
// 默认连接池
// 1:创建Jedis连接池
JedisPool pool = new JedisPool("localhost", 6379);
// 2:从连接池中获取Jedis对象
Jedis jedis = pool.getResource();
/* 设置密码
jedis.auth(密码); */
// 3:TODO
System.out.println(jedis);
// 4:关闭资源
jedis.close();
pool.destroy();
}
需求-->添加:name=gutianle age = 18
1>string类型
public class StringJedisTest {
public static void main(String[] args) {
// 默认连接池
// 1:创建Jedis连接池
JedisPool pool = new JedisPool("localhost", 6379);
// 2:从连接池中获取Jedis对象
Jedis jedis = pool.getResource();
/* 设置密码
jedis.auth(密码); */
// 3:TODO
// 需求-->添加 name = gutianle age = 18
// jedis中方法就是redis的命令
// String类型
jedis.set("name","gutianle"); // 设置名字
jedis.set("age","18"); // 设置年龄 18
jedis.incr("age"); // 增长年龄 19
jedis.decr("age"); // 减少年龄 18
jedis.del("age"); // 删除键值对 null
jedis.setex("name",5,"pyy");
// 获取value值
System.out.println(jedis.get("name"));
System.out.println(jedis.get("age"));
// 可以查询出当前的key还剩余多长时间过期
System.out.println(jedis.ttl("name"));
// 4:关闭资源
jedis.close();
pool.destroy();
}
}
2>Hash类型
public class HashJedisTest {
public static void main(String[] args) {
// 默认连接池
// 1:创建Jedis连接池
JedisPool pool = new JedisPool("localhost", 6379);
// 2:从连接池中获取Jedis对象
Jedis jedis = pool.getResource();
/* 设置密码
jedis.auth(密码); */
// 3:TODO
// 需求-->添加 name = gutianle age = 18
// jedis中方法就是redis的命令
// hash类型
jedis.hset("user2","name","pyy"); // 设置名字
jedis.hset("user2","age","26"); // 设置年龄
// 是否存在
System.out.println(jedis.hexists("user2", "name")); // true
// 获取value值
System.out.println(jedis.hvals("user2")); // [26,pyy]
// 获取集合内所有对象
System.out.println(jedis.hgetAll("user2")); // {age=26, name=pyy}
// 获取集合的长度/个数
System.out.println(jedis.hlen("user2")); // 长度:2
// 获取key值对应的value值
System.out.println(jedis.hget("user2", "name")); // pyy
System.out.println(jedis.hget("user2", "age")); // 26
// 4:关闭资源
jedis.close();
pool.destroy();
}
}
3>List类型
public class ListJedisTest {
public static void main(String[] args) {
// 默认连接池
// 1:创建Jedis连接池
JedisPool pool = new JedisPool("localhost", 6379);
// 2:从连接池中获取Jedis对象
Jedis jedis = pool.getResource();
/* 设置密码
jedis.auth(密码); */
// 3:TODO
// 需求-->随意
// jedis中方法就是redis的命令
// List类型
// key值要加list或者写别的名字
//不然会报堆栈异常 因为缓存里面可能还创过之前的user的String类型
jedis.rpush("user-list","java");
jedis.rpush("user-list","C++");
jedis.rpush("user-list","C#");
List<String> list = jedis.lrange("user-list", 0, -1);
// 列出list集合中的元素
for (String string : list) {
System.out.println(string); // 获取存储的value
}
// 测试keys * 全局遍历key值
Set<String> keys = jedis.keys("*");
Iterator<String> it=keys.iterator() ;
while(it.hasNext()){
String key = it.next();
System.out.println(key);
}
// 4:关闭资源
jedis.close();
pool.destroy();
}
}
4>Set类型
public class SetJedisTest {
public static void main(String[] args) {
// 默认连接池
// 1:创建Jedis连接池
JedisPool pool = new JedisPool("localhost", 6379);
// 2:从连接池中获取Jedis对象
Jedis jedis = pool.getResource();
/* 设置密码
jedis.auth(密码); */
// 3:TODO
// 需求-->随意
// jedis中方法就是redis的命令
// List类型
// key值要加list或者写别的名字
//不然会报堆栈异常 因为缓存里面可能还创过之前的user的String类型
jedis.sadd("user-set","man");
jedis.sadd("user-set","girl");
jedis.sadd("user-set","null");
Set<String> smembers = jedis.smembers("user-set");
// 列出set集合中的元素
for (String string : smembers) {
System.out.println("获取的value:" + string); // 获取存储的value
}
System.out.println("-------------");
// 删除set集合中的元素
jedis.srem("user-set","null");
// 列出删除后set集合中的元素
Set<String> smembers2 = jedis.smembers("user-set");
for (String string : smembers2) {
System.out.println("获取的value:" + string); // 获取存储的value
}
System.out.println("----------");
// 随机弹出集合中的元素
jedis.spop("user-set",1);
// 列出随机弹出后set集合中的元素
Set<String> smembers3 = jedis.smembers("user-set");
for (String string : smembers3) {
System.out.println("获取的value:" + string); // 获取存储的value
}
// 返回set集合中的元素个数
System.out.println(jedis.scard("user-set")); // 1
// 4:关闭资源
jedis.close();
pool.destroy();
}
}
5>SortedSet类型
public class SortedSetJedisTest {
public static void main(String[] args) {
// 默认连接池
// 1:创建Jedis连接池
JedisPool pool = new JedisPool("localhost", 6379);
// 2:从连接池中获取Jedis对象
Jedis jedis = pool.getResource();
/* 设置密码
jedis.auth(密码); */
// 3:TODO
// 需求-->随意
// jedis中方法就是redis的命令
// Sorted类型 有序 唯一
//不然会报堆栈异常 因为缓存里面可能还创过之前的user的String类型
// String key, double score, String member
jedis.zadd("user-sorted",2000,"A");
jedis.zadd("user-sorted",1500,"B");
jedis.zadd("user-sorted",3000,"C");
System.out.println(jedis.zrangeByScore("user-sorted", 1, 100000));
// 升序排列打印出对应的member值
System.out.println(jedis.zrange("user-sorted", 0, -1)); // [B, A, C]
// 降序排列打印出来对应的member值
System.out.println(jedis.zrevrange("user-sorted", 0, -1)); // [C, A, B]
// 4:关闭资源
jedis.close();
pool.destroy();
}
}
2)v2版本(定制连接池)
public static void main2(String[] args) {
// 定制连接池
GenericObjectPoolConfig config = new GenericObjectPoolConfig();
最大连接数, 默认8个
config.setMaxTotal(100);
最大空闲连接数, 默认8个
config.setMaxIdle(20);
//获取连接时的最大等待毫秒数(如果设置为阻塞时BlockWhenExhausted),如果超时就抛异常, 小于零:阻塞不确定的时间, 默认-1
config.setMaxWaitMillis(-1);
//在获取连接的时候检查有效性, 默认false
config.setTestOnBorrow(true);
JedisPool pool = new JedisPool(config,"192.168.122.128",6379,5000,"wolfcode");
Jedis j = pool.getResource();
String name = j.get("name");
System.out.println(name);
j.close();
pool.close();
pool.destroy();
}
集成SpringBoot
1>导入依赖
2>配置application.properties
3>设置启动类 App新建测试类
RedisTest(记得贴SpringBootTest注解)
springboot-redis的方法就是redis的命令全称
1>Stirng类型
@SpringBootTest
public class StringRedisTest {
@Autowired
// kv都是String类型
// redis存数据是list集合 指定list集合中的元素
private StringRedisTemplate template;
@Test
public void testRedis(){
// springboot-redis方法就是redis命令的全称
// 需求-->随意
// opsForValue 工具类
template.opsForValue().set("nameString","gutianle");
template.opsForValue().set("ageString","18");
System.out.println(template.opsForValue().get("nameString"));
// 递增 递减改为-值
System.out.println(template.opsForValue().increment("ageString", 1)); // 19
// 将 key 的值设为 value ,当且仅当 key 不存在 t不存在 f 存在
System.out.println(template.opsForValue().setIfAbsent("nameString2", "pyy")); // true
// key存在设置新值,并返回旧值
System.out.println(template.opsForValue().getAndSet("ageString", "26")); // 19
// 获取修改以后的ageString的value
System.out.println(template.opsForValue().get("ageString")); // 26
}
}
2>Hash类型
@SpringBootTest
public class HashRedisTest {
@Autowired
// kv都是String类型
// redis存数据是list集合 指定list集合中的元素
// private RedisTemplate<key,value>template;
private StringRedisTemplate template;
@Test
public void hashTestRedis(){
// 需求-->存name='gutianle'
// opsForValue 工具类
// 往hash集合存入k-v
// springboot-redis方法就是redis命令的全称
template.opsForHash().put("user-hash","name","zzl");
template.opsForHash().put("user-hash","age","26");
// 往hash集合获取value值
System.out.println(template.opsForHash().get("user-hash", "name"));
// 获取hash集合对应的所有k-v值
System.out.println(template.opsForHash().entries("user-hash")); // {name=zzl, age=26}
// 判断是否存在key值
System.out.println(template.opsForHash().hasKey("user-hash", "name")); // true
// hash递增 如果不存在,就会创建一个 并把新增后的值返回
// 递减 将最后的delta值改为负数
System.out.println(template.opsForHash().increment("user-hash", "age", 1)); // 27
}
}
3>List类型
@SpringBootTest
public class ListRedisTest {
@Autowired
// kv都是String类型
// redis存数据是list集合 指定list集合中的元素
private StringRedisTemplate template;
@Test
public void testRedis(){
// springboot-redis方法就是redis命令的全称
// 需求-->随意
// opsForXxxx 工具类
// 队列右存入数据 有序不唯一
// template.opsForList().rightPush("myList", "money"); //操作list
// template.opsForList().rightPush("myList", "phone");
// template.opsForList().rightPush("myList", "computer");
// 获取内容
System.out.println(template.opsForList().range("myList", 0, -1)); //[money, phone, computer]
// 获取长度
System.out.println(template.opsForList().size("myList"));
// 根据索引获取对应的value值
System.out.println(template.opsForList().index("myList", 2)); //computer
// 根据索引修改某条数据
template.opsForList().set("myList", 3, "telephone");
// 获取修改以后的内容
System.out.println(template.opsForList().range("myList", 0, -1));
// [money, phone, computer, telephone, phone, computer]
}
}
4>Set类型
@SpringBootTest
public class SetRedisTest {
@Autowired
// kv都是String类型
// redis存数据是list集合 指定list集合中的元素
private StringRedisTemplate template;
@Test
public void testRedis(){
// set集合 无序唯一
// 需求-->随意
// opsForXxxx 工具类
// 存入kv
template.opsForSet().add("setList","pyy","gtl","gaga","ldy"); //操作set
template.opsForSet().add("setList2","pyy","gtl2","gaga2","ldy"); //操作set
template.opsForSet().remove("setList2", "pyy2","ldy2"); // 移除value
// 根据value从一个set中查询,是否存在
System.out.println(template.opsForSet().isMember("setList", "ldy")); // T
// 根据key获取Set中的所有值
System.out.println(template.opsForSet().members("setList")); // [pyy, ldy, gtl, gaga]
System.out.println(template.opsForSet().members("setList2")); // [ldy, pyy, gaga2, gtl2]
System.out.println("------------");
// 返回key1中特有元素(差集)
System.out.println(template.opsForSet().difference("setList", "setList2")); //[gaga, gtl]
System.out.println(template.opsForSet().difference("setList2", "setList")); //[gaga2, gtl2]
// 返回两个set集合的交集-->去重
System.out.println(template.opsForSet().intersect("setList", "setList2")); //[pyy, ldy]
// 返回两个set集合的并集
System.out.println(template.opsForSet().union("setList", "setList2"));
// [pyy, ldy, gaga2, gtl2, gtl, gaga]
}
}