Redis

NoSQL



NoSQL特点

1、方便扩展(数据之间没有关系,很好扩展!)

2、大数据量高性能(Redis一秒写8万次,读取11万,NoSQL的缓存记录级,是一种细粒度的缓存,性能会比较高)

3、数据类型是多样型的!(不需要事先设计数据库!随取随用!如果是数据量十分大的表,很多人就无法设计了)

4、传统RDBMS和NoSQL

传统的RDBMS
- 结构化组织
- SQL
- 数据和关系都存在单独的表中
- 操作操作,数据定义语言
- 严格的一致性
- 基础的事务
Nosql
- 不仅仅是数据
- 没有固定的查询语言
- 键值对存储,列存储,文档存储,图形数据库(社交关系)
- 最终一致性,
- CAP定理 和 BASE(异地多活)
- 高性能,高可用,高可扩


3V+V高

  • 大数据时代的3V:主要是描述问题的

​ 1.海量Volume

​ 2.多样Variety

​ 3.实时Velocity

  • 大数据时代的3高:主要是对程序的要求

​ 1.高并发

​ 2.高可拓

​ 3.高性能



四大分类

  • KV键值对

  • 文档型数据库(bson格式和json一样)

    MongoDB(一般必须要掌握)

    MongoDB是一个基于分布式文件存储的数据库,C+编写,主要用来处理大量的文档!

    MongoDB是一个介于关系型数据库和非关系型数据中中间的产品!MongoDB是非关系型数据库中功能最丰富,最像关
    系型数据库的!


  • 列存储数据库

    HBase
    分布式文件系统


  • 图关系数据库

    不是存图片,是存关系(类似:社交)


四大数据库区别

image-20230813135003503






Redis

Attempt

概述

Redis(Remote Dictionary Server),即远程字典服务

一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Vlue数据库,并提供多种语言的API


Redis功能

1、内存存储、持久化,内存中是断电即失、所以说持久化很重要(rdb、aof)

2、效率高,可以用于高速缓存

3、发布订阅系统、

4、地图信息分析

5、计时器、计数器(浏览量!)


特性

1、多样的数据类型
2、持久化
3、集群
4、事务



安装

Wdinow –》Github :Release 3.2.100 · microsoftarchive/redis · GitHub


Window

1、启动:redis-server.exe

2、使用Redis客户端(redis-cli.exe)连接redis

img



Linux

Linux下安装Redis详解

  • 宝塔

1、解压

#移动到opt文件夹解压
tar -zxvf redis-7.0.12.tar.gz

2、进入解压后的文件


3、安装c++

yum install gcc-c++

4、配置所有的文件

make

make install

5、redis的默认安装路径、/usr/local/bin

image-20230814215837756


6、将redis的配置文件拷贝到 /usr/local/bin/自定义文件夹/,以后所有使用的配置文件使用其中的

mkdir chenconfig

cp /opt/redis-7.0.12/redis.conf chenconfig

7、修改配置文件为 默认启动

  • chenconfig/redis.conf
daemonize yes

启动、连接、关闭

#进入目标文件夹/usr/local/bin   -> 选择配置文件  -》 启动
redis-server chenconfig/redis.conf

#使用Redis客户端进行连接
redis-cli -p 6379
ping

#关闭redis
shutdown
exit

#查看当前redis进程
ps -ef|grep redis


#Docker直接拉取 使用====================
docker pull redis

#创建容器===============================
#数据卷挂载
mkdir -p /mydata/redis/conf/
mkdir -p /mydata/redis/data/
chmod -R 777 /mydata/
cd /mydata/redis/conf



#拉取官网con文件
wget http://download.redis.io/redis-stable/redis.conf

#1创建容器
sudo docker run -p 6379:6379 --name redis -v /mydata/redis/conf:/etc/redis/redis.conf -v /mydata/redis/data:/data -d redis redis-server /etc/redis/redis.conf --appendonly yes --requirepass "密码"


#2启动
docker start redis

#连接客户端=============================
docker exec -it redis redis-cli

#密码验证 ======================
auth 密码

#数据持久化=============================
cd /mydata/redis/conf
vi redis.conf

#添加以下====================
# 持久化
appendonly yes
# 允许外网访问 yes-不运许外网访问 no- 禁用保护模式。允许本地和远程连接。
protected-mode no
# 允许后台运行 yes-运行后台运行 no-不运许
daemonize yes
# 监听访问ip,指定的ip才能访问(注释掉或指定IP)
bind 127.0.0.1
# redis访问密码
requirepass 密码





benchmark 性能测试

image-20230814213618174


# 测试:100个并发连接,100000请求
redis-benchmark -h localhost -p 6379 -c 100 -n 100000

image-20230814221719582






基础知识

@Autowired
private StringRedisTemplate stringRedisTemplate;

redis默认有16个数据库,默认第0个数据库

#切换数据库
select 数据库下标

#当前db大小
dbszie / DBSIZE

#查看所有的key
keys *
stringRedisTemplate.opsForValue().getOperations().keys("*")

#清除当前数据库
flushdb

#清除全部数据库
flushall
//获取redis的连接对象
RedisConnection connection = stringRedisTemplate.getConnectionFactory().getConnection();
connection.flushDb();
connection.flushAll();

Redis是单线程的:官方表示,Redis;是基于内存操作,CPU不是Redis’性能瓶颈,Redis的瓶颈是根据机器的内存和网络带宽

五大数据类型

image-20230815000746608


Redis-Key

#查看全部key
keys *

#设置kv -- set
set key value
stringRedisTemplate.opsForValue().set("key","value");

#获取value -- get
get key
stringRedisTemplate.opsForValue().get("key")

#获取当前key类型 -- type
type key

#判断当前key是否存在 -- exists
exists key

#移除当前的key -- move
move key dbNumbern

#设置有效时间 -- expire
expire key time(s)
stringRedisTemplate.opsForValue().getAndExpire("key", Duration.ofDays(Time))
#查看有效时间 -- ttl
ttl key


String(字符串)

#存多
mset Key Value Key Value


#追加字符串 , 如果当前key不存在,就相当于set一个key -- append key string
append key addValue

#获取字符串长度 -- strlen
strlen key

#自增 +1 -- incr
incr key
stringRedisTemplate.opsForValue().increment("key")
#自减1 -- decr
decr key

#步长增量 -- incrby
incrby key n
#步长减量 -- decrby
decrby key n

#字符范围range -- getrange #截取字符串[startIndex,endIndex]
getrange key startIndex endIndex

#替换字符串 -- setrange #从下标index开始替换为string字符串
SETRANGE key index string


#setnx (set if not exist)不存在在设置(在分布式锁中会常常使用!)
setnx key string #如果 key 不存在,创建key,值为value / 如果 key 存在,创建失败

#setex (set with expire) 设置过期时间
setex key time string #设置 key 的值为 string ,time秒后过期
stringRedisTemplate.boundValueOps("moeny").set("99",5, TimeUnit.MINUTES);

#分布式锁
set key value NX EX 300 #NX:不存在才创建 EX:过期时间
SET productld:lock 0xx9p03001 NX EX 30000

#对象
set user:1 {name:chen,age:2}


String类以的使用场景:value除了是我们的字符串还可以是我们的数字!

  • 计数器
  • 统计多单位的数量
  • 对象缓存存储!


List(列表)

基本类型:列表

image-20230815111243775

在redis里面,Iist可设置成为,栈、队列、阻塞队列!

所有的List命令都是用 l 来头的

###########################################  存  ###########################################
#左存 lpush list_key value --将一个值或者多个值,插入到列表头部(左) 54321
lpush mylist value
#右存 rpush list_key value --将一个值或者多个值,插入到列表尾部(右) 12345
rpush mylist value

#区间范围获取 lrange list_key 0 -1 --》全部
lrange mylist starIndex endIndex

########################################### 取 ###########################################
#左移除 lpop list_key
lpop mylist
#右移除 rpop list_key
rpop mylist

#移除最后一个元素,添加到新的列表中 rpoplpush list_key newList_key
rpoplpush mylist mylist2

# 通过下标获取list中的值 lindex list_key index
lindex mylist 1

#获取长度 llen list_key
llen mylist

#移除指定的value lrem list_key 个数 vakue
lrem mylist 1 0

#修剪提取 ltrim list_key startIndex endIndex #通过下标截取提取出指定的长度
ltrim mylist 1 2

# 替换 lset list_key index newValue 将列表中指定下标的值替换为另外一个值,更新操作
EXISTS mylist #先判断list存不存在
lset mylist 0 chen #如果存在,更新当前下标的值,如果不存在,则会报错!

# 具体前后位置插入 linsert
# 在指定的值 前/后插入 linsert list_key before/after 指定的值 待插入的值
linsert mylist before/after a b

总结:

  • 实际上是一个链表,before Node after,left,right都可以插入值
  • 如果key不存在,创建新的链表
  • 如果key存在,新增内容
  • 如果移除了所有值,空链表,也代表不存在!
  • 在两边插入或者改动值,效率最高!中间元素,相对来说效率会低一点


set(无序集合)

  • set中的值不能重复
#添加  sadd set_key value
sadd myset chen

#查看指定set 的所有值 smembers set_key
smembers myset

#判断某一个值是否在set中 sismembers set_key value
sismembers myset chen

#获取set集合中的内容元素个数 scard set_key
scard myset

# 随机抽选出一个元素 srandmember set_key 随机抽选出n个元素 srandmember set_key n
srandmember myset

# 将集合中的指定值一定到另一个集合 smove setA setB setA_value
smove myset1 myset2 chen


#####################################
# 差集 sdiff setA setB
sdiiff seta setb

# 交集 sinter setA setB
sinter seta setb

# 并集 sunion setA setB
sunion seta setb


Hash(哈希)

Map集合,key-map 时候这个值是一个map集合!

  • h
##################################################################

#存1 hset Hash_Key Key Value
hset myhast chen 12

#存多 hmset Hash_Key Key1 Value1 Key2 Value2
hmset myhash chen 1 peng 2

#取 hget Hash_Key Key
hget myhast chen

#取多 hmget Hash_Key Key1 Key2
hmget myhash chen peng

#取全部 hgetall Hash_Key
hgetall myhash

#删除 hdel Hash_key Key
hdel myhash chen

##################################################################

#获取长度 hlen Hash_Key
hlen myhash

#判断是否存在 hexists Hash_key Key
hexists myhash peng

#获取全部Key hkeys Hash_Key
hkeys muhash

#获取全部Value hvals Hash_Key
hvals muhash

##################################################################
#自增 hincrby Hash_Key Key +Number
hincrby myhash a 1

#hsetnx 判断是否存在,不存在则创建 hsetnx Hash_Key Key Value
hsetnx myhash a 1


hash变更的数据user name age,尤其是是用户信息之类的,经常变动的信息!hash更适合于对象的存储,String更加适合字符串存储!



zset(有序集合)

  • 在set的基础上新增位置选择,可实现排序(将参与排序的字段设置为位置号码)
######################################################################

#添加 zadd Zset_Key 位置号码 Value
zadd myzset 0 chen

#添加多个 zadd Zset_Key 位置号码 Value 位置号码 Value
zadd myzset 0 chen 1 peng

#查看 zrange Zset_Key start index
zrange myzset 0 -1

######################################################################
#排序的实现 (排序字段设置为位置号)
zadd money 100 a 200 b 300 c 5000 d 6000 e 7000 f
zrangebyscore money -inf +inf #升序
zrevrange money 0 -1 #降序
zrangebyscore money -inf +inf withscores #携带位置


#移除元素 zrem Zset_Key Value
zrem money f

#查看元素个数 zcard Zset_Key
zcard myzset

#获取位置区间的元素个数 zcount Zset_Key start end
zcount chen 1 3

案例思路:st排序存储班级成绩表,工资表排序!

普通消息,1,重要消息2,带权重进行判断!

排行榜应用实现






三大特殊类型数据


geospatial 地理位置

  • 底层还是 zser (可使用 zrem删除、zrange查看)

测试数据:经纬度查询定位

geoadd : 往集合添加元素

#加地理位置
#参数 key 值 (经度,纬度,名称)
geoadd china:city 113.27079 23.13567 guangzhou


**geopos ** : 获取

#单一的经纬度
geopos china:city guangzhou

geohash : 获取集合中的元素的经纬度返回的是哈希值:降为一维

geohash china:city shenzheng

geodist: 两点距离

#单位 m(默认) km mi(英里) ft(英尺) 
geodist china:city guangzhou shenzheng 单位

georadius : 以当前经纬度为中心寻找某半径的元素

  • 获取指定的数量:count x
  • 获取之间距离:withdist
  • 获取对方的经纬度:withcoord
 georadius china:city 中心经纬度 半径长度 单位

georadius china:city 中心经纬度 半径长度 单位 count 数量 withdist withcoord

georadiusbymember :以列表中某一地点为中心寻找半径内的元素

georadiusbymember china:city shenzheng 1000 km





Hyperloglog 基数统计

优点:占用的内存是固定,2^64不同的元素的技术,只需要废12KB内存!

网页的UV(一个人访问一个网站多次,但是还是算作一个人)

PV:可重复

传统的方式,set保存用户的id,然后就可以统计set中的元素数量作为标准判断!

这个方式如果保存大量的用户d,就会比较麻烦!我们的目的是为了计数,而不是保存用户id;


# 创建一组元素 pfadd key_Name data data data data data ...
pfadd mykey a b c d e f g

# 统计mykey元素的基数数量 pfcount key_Name
pfcount mykey

# 合并两组 pfmerge 并集key key1 key2 key1 key2-->并集key
pfmerge mykey3 mykey1 mykey2





Bitmaps 位图场景

位存储

统计用户信息,活跃,不活跃!登录、未登录!打卡,365打卡!两个状态的,都可以使用Bitmaps!
Bitmaps位图,数据结构!都是操作二进制位来进行记录,就只有0和1两个状态


setbit : 记录某一天的状态 0 / 1

#====存
#例如一周打卡 : 0-6
setbit sign 0 1
setbit sign 1 1

#====取
getbit sign 0 # -> 1


#=====统计true数量 ->1
bitcount sign





事务

Rdis事务本质:一组命令的集合!一个事务中的所有命令都会被序列化,在事务执行过程的中,会按照顺序执行!

一次性、顺序性、排他性!执行一些列的命令!

Redis事务没有没有隔离级别的概念!

所有的命令在事务中,并没有直接被执行!只有发起执行命令的时候才会执行!Exec

==Redis.单条命令式保存原子性的1,但是事务不保证原子性!==

redis的事务:

  • 开启事务(multi)
  • 命令入队(…)
  • 执行事务(exec)

正常执行事务

#开启事务  --  multi
multi

#操作入队
127.0.0.1:6379(TX)> set k1 1
QUEUED
127.0.0.1:6379(TX)> set k2 2
QUEUED
127.0.0.1:6379(TX)> set k3 3
QUEUED

#一次性按顺序执行事务 -- 执行完事务关闭
exec

放弃执行事务

#放弃事务 不执行 -- discard  取消事务(事务中的命令不会被执行)
discard

注意:

  • 偏译型异常(代码有问题!命令有错!),事务中所有的命令都不会被执行!

  • 运行时异常(1/0),如果事务队列中存在语法性,那么执行命令的时候,其他命令是可以正常执行的,错误命令抛出异常!——- 不存在原子性






Redis实现乐观锁

Watch : 监控

unwatch : 取消监控

  • 在准备实现事务操作的时候,对某个key进行watch监控,这样在最后exec执行事务的时候,如何发现和监控的时候的数据不匹配则执行事务失败,防止其他线程插队进行更改,实现上锁

  • 事务执行失败,则先解锁再对新的值上锁

watch money
:OK
multi
:OK
decrby money 30
:QUEUED
incrby out 30
:QUEUED
exec #执行事务前,其他线程进行了对money进行了修改,则事务执行失败
:(nil)






Jedis

Jedis是Redis官方推荐的java连接开发工具!使用java操作Redis中间件

1、依赖

<!--导入jedis包-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>4.4.3</version>
</dependency>
<!--fastjson-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.83</version>
</dependency>

2、编码测试

  • 连接数据库
//1、new Jedis对象
Jedis jedis = new Jedis("127.0.0.1",6379);

//2、jedis 所有的命令就是全部基本redis命令
System.out.println(jedis.ping()); //测试连接 --》 PONG

  • 操作命令
//1、new Jedis对象
Jedis jedis = new Jedis("127.0.0.1",6379);

//2、jedis 所有的命令就是全部基本redis命令
System.out.println(jedis.ping());
jedis.flushDB();

System.out.println("清空数据:"+jedis.flushDB());
System.out.println("判断某个键是否存在:"+jedis.exists("username"));
System.out.println("新增<'username','chen'>的键值对:"+jedis.set("username'","chen"));
System.out.println("新增<'password','password'>的键值对:"+jedis.set("password","password"));
System.out.println("系统中所有的键如下:");
Set<String> keys = jedis.keys("*");
System.out.println(keys);
System.out.println("password:"+jedis.del("password"));
System.out.println("判断键password是否存在:"+jedis.exists("password"));
System.out.println("查看键username所存储的值的类型:"+jedis.type("username"));
System.out.println("随机返回key空间的一个:"+jedis.randomKey());
System.out.println("取出改后的name:"+jedis.get("name"));
System.out.println("按索引查询:"+jedis.select(0));
System.out.println("删除当前选择数据库中的所有key:"+jedis.flushDB());
System.out.println("返回当前数据库中key的数目:"+jedis.dbSize());
System.out.println("删除所有数据库中的所有key:"+jedis.flushAll());

  • 断开连接
jedis.close;





SpringBoot整合

Reids工具类

1、依赖项

image-20230816002338503

<!--spring data redis & cache-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
<!-- redis依赖commons-pool 这个依赖一定要添加 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>

说明:在SpringBoot22.x之后,原来使用的jedis被替换为了lettuce

jedis:采用的直连,多个线程操作的话,是不安全的,如果想要避免不安全的,使用jedis pool连接池!

lettuce:采用netty,实例可以再多个线程中进行共享,不存在线程不安全的情况!



2、配置

  • 源码 RedisAutoConfiguration
# 配置redis
spring:
redis:
host: 192.168.238.3
port: 6379
lettuce:
pool: #连接池
max-active: 8 #最大连接
max-idle: 8 #最大空闲连接
min-idle: 0 #最小空闲连接
max-wait: 100 #连接等待时间



3、测试

RedisTemplate

    @Autowired
private RedisTemplate redisTemplate; //需要序列化,否则乱码,或者泛型设置为<String,String>

@Test
void contextLoads() {

//redisTemplate
//opsForValue 作字符串 类似String
//opsForList 操作list 类似List
//opsForSet 操作set 类似set
//opsForHash 操作hash 类似hash
//opsForGeo 地图
//opsForZSet
//opsForHyperLogLog
//...

//除了基本操作,常用的方法可以直接通过redisTemplate操作,比如事务,增删改查

//获取redis的连接对象 -- 很少用
// RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
// connection.flushDb();
// connection.flushAll();

redisTemplate.opsForValue().set("mykey","辰呀");
System.out.println(redisTemplate.opsForValue().get("mykey"));


// --------- 对象
User user = new User("辰呀", 9);
redisTemplate.opsForValue().set("user",user);
System.out.println(redisTemplate.opsForValue().get("user")); //自动反序列化成java对象输出

}

StringRedisTemplate

//如果存的是String,使用 StringRedisTemplate 
@Resource
private StringRedisTemplate stringRedisTemplate;

private static final ObjectMapper mapper = new ObjectMapper(); //序列化工具



User user = new User("辰呀", 9);
//手动序列化
String s = mapper.writeValueAsString(user);

stringRedisTemplate.opsForValue().set("user",s);
String value = stringRedisTemplate.opsForValue().get("user");

//手动反序列化
mapper.readValue(value, User.class);




//---------------------------字符串---------------------------

//把验证码存入Redis
String key = KEY_SMS_CODE_REG + phone; //前缀
stringRedisTemplate.boundValueOps(键).set(值,5, TimeUnit.MINUTES); //value值,set设置5分钟过期
Long incr = stringRedisTemplate.boundValueOps(键).increment(); //increment:键自增+1

//取值 使用模板
stringRedisTemplate.hasKey(key) //判断key值是否存在
String querySmsCode = stringRedisTemplate.boundValueOps(key).get(); //取值


//---------------------------Zset---------------------------
stringRedisTemplate.boundZSetOps(Zset的键).add(键,值);


4、序列化

  • 解决写入乱码问题(传递是对象的时候需要对对象进行序列化,两种方法:①转为json ②实体类上实现序列化)
  • 数据的存储和传输
//1、转为json:所有的对象,需要系列化,通过转为json
String s = new ObjectMapper().writeValueAsString(user);
redisTemplate.opsForValue().set("user",s);

//2、对象序列化:实现 Serializable 接口
public class User implements Serializable {
}


5、自定义RedisTemplate

  • 配置类
@Configuration
public class RedisConfig {

// 编写自定义redisTemplate
@Bean
//@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
@SuppressWarnings("all")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<String,Object>();
template.setConnectionFactory(redisConnectionFactory);
Jackson2JsonRedisSerializer<Object> objectJackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(Object.class);
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
objectJackson2JsonRedisSerializer.setObjectMapper(mapper);
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();

//key采用string的序列化形式
template.setKeySerializer(stringRedisSerializer);
//hash的key也采用String的序列化形式
template.setHashKeySerializer(stringRedisSerializer);
//value序列化形式采用jackson
template.setValueSerializer(objectJackson2JsonRedisSerializer);
//hash的value序列化方式采用jackson
template.setHashKeySerializer(objectJackson2JsonRedisSerializer);
//配置具体的序列化方式

template.afterPropertiesSet();
return template;
}

}





Redis.conf

  • 配置文件
cd /mydata/redis/conf
vi redis.conf


#保护模式:允许外网访问 yes-不运许外网访问 no-允许
protected-mode no

#守护进程的方式运行:允许后台运行 yes-运行后台运行 no-不运许
daemonize yes

# 监听访问ip,指定的ip才能访问(注释掉或指定IP)
bind 127.0.0.1


#以后台的方式运行,需要指定一个pid文件
pidfile /var/run redis_6379.pid

#日志
loglevel notice
logfile "文件名"

#数据库数量
database 16 #默认16


快照

持久化,在规定的时间内,执行了多少次操作,则会持久化到文件.rdb .aof

#如果 900秒 内,至少有 1 个key进行了修改,则进行持久化操作
save 900 1
save 300 10


#持久化出错后,是否继续进行工作
stop-writes-on-bgsave-error yes

#是否压缩rdb文件,消耗cpu资源
rdbcompression yes

#保存rdb文件的时候,进行错误的检查校验
rdbchecksum yes

dir ./ #rdb文件保存的目录


security : 安全

#获取密码
config get requirepass

# 配置文件设置redis访问密码
requirepass root

#设置密码
config set requirepass "密码"

#密码验证
auth 密码



clients : 客户端

#设置最大能连接上redis的最大客户端的数量
maxclients 10000

#redis配置的最大内存容量
maxmemory <bytes>

#内存到达上限之后的处理策略
maxmemory-policy noeviction

  当 Redis 内存使用达到 maxmemory 时,需要选择设置好的 maxmemory-policy 进行对数据进行淘汰机制。

1.volatile-lru(least recently used):最近最少使用算法,从设置了过期时间的键key中选择空转时间最长的键值对清除掉;

2.volatile-lfu(least frequently used):最近最不经常使用算法,从设置了过期时间的键中选择某段时间之内使用频次最小的键值对清除掉;

3.volatile-ttl:从设置了过期时间的键中选择过期时间最早的键值对清除;

4.volatile-random:从设置了过期时间的键中,随机选择键进行清除;

5.allkeys-lru:最近最少使用算法,从所有的键中选择空转时间最长的键值对清除;

6.allkeys-lfu:最近最不经常使用算法,从所有的键中选择某段时间之内使用频次最少的键值对清除;

7.allkeys-random:所有的键中,随机选择键进行删除;

8.noeviction:不做任何的清理工作,在redis的内存超过限制之后,所有的写入操作都会返回错误;但是读操作都能正常的进行;


append only模式 aof配置

appendonly no  # 默认不开启aof模式,默认是使用rdb反射光持久化
appendfilename "appendonly.aof" #持久化的文件的名字

appendfsync always #每次修改都会 sync 消耗性能
appendfsync everysec #每秒执行一次sync,可能会丢这1s的数据
appendfsync no #不执行 sync 这个时候操作系统自己同步数据,速度最快





Redis持久化

Rdis是内存数据库,如果不将内存中的数据库状态保存到磁盘,那么一旦服务器进程退出,服务器中的数据库状态也会消失。所以Redis提供了持久化功能!


RDB(Redis DataBase)

image-20230921204919647

#如果 900秒 内,至少有 1 个key进行了修改,则进行持久化操作
save 900 1
save 300 10


#持久化出错后,是否继续进行工作
stop-writes-on-bgsave-error yes

#是否压缩rdb文件,消耗cpu资源
rdbcompression yes

#保存rdb文件的时候,进行错误的检查校验
rdbchecksum yes

dir ./ #rdb文件保存的目录

rdb持久化触发规则

1、save的规则满足的情况下,会自动触发rdb规则

2、执行flushall命令,也会触发我们的rdb规则I

3、退出redis,也会产生rdb文件!

恢复rdb文件

  • 只需要将rdb文件放在我们redis,启动目录就可以,redis启动的时候会自动检查dump.rdb恢复其中的数据
  • 查看需要存放的位置,连接后查看
127.0.0.1:6379> config get dir
1) "dir"
2) "/data" #如果这个目录下存在 dump.rdb 文件,启动就会自动恢复其中的数据

AOF(Append Only File)

将所有的命令全部都记录下来,history,恢复的时候就把这个文件命令全部执行一遍

image-20230921210853406

# 开启AOF  -- yes  
appendonly no

#修复AOP文件(如果AOP文件出现错误,redis无法启动,使用工具对aop文件进行修改更改)
redis-check-aof --fix appendonly.aof
appendonly no  # 默认不开启aof模式,默认是使用rdb反射光持久化
appendfilename "appendonly.aof" #持久化的文件的名字

appendfsync always #每次修改都会 sync 消耗性能
appendfsync everysec #每秒执行一次sync,可能会丢这1s的数据
appendfsync no #不执行 sync 这个时候操作系统自己同步数据,速度最快





Redis发布订阅

订阅 subscribe

#订阅频道 subscribe 频道
subscribe chen

#订阅多个 psubscribe 频道1 频道2
psubscribe chen peng

#退订频道 unsubscribe 频道

发布 publish

#频道发布消息  publish 频道 消息
publish chen hello





Redis主从复制

image-20230921213228968


环境配置

  • 只需要配置从库,不用配置主库(默认是主库)
info replication  #查看当前库的信息
127.0.0.1:6379> info replication
# Replication
role:master #角色 master
connected_slaves:0 #没有从机

  • 集群搭建


一主二从

==默认情况下,每台Redis服务器都是主节点==,配置从机就好

  • 配置从机后,主机会发送整个数据文件到从机,完成数据同步
  • **全量复制 : ** 数据同步,连接上主机
  • 增量复制 : 数据同步后主机每次更改都会同步过去
#命令行配置从机  -- 永久
#绑定主机 slaveof 地址 端口
salveof 192.168.238.3 6379

  • 只有主机可以,从机只能,主机内容会被同步到从机

主机宕机

主机断开连接,从机依旧连接到主机的,但是没有写操作,这个时候,主机如果回来了,从机依旧可以直接获取到主机写的信息!


从机宕机

如果是使用命令行,来配置的主从,这个时候从机如果更启了,数据丢失,就会变回主机,但要设置回从机后,立马获取回主机的全部数据



两主一从

主机 – 从机(下从机的主机) – 从机

  • 中间的依然是从机(solve)身份:无法写入,只能读

变为主机–主机宕机:其他节点手动连接到该新的主节点

  • 主机重启后,该节点依然为主节点,只能重新配置为从节点
#当前从机设置为主机
slaveof no one


哨兵模式【*】

  • 老大宕机后:自动选取老大模式

image-20230921232830659


防止单个哨兵下线

image-20230921233123420


测试:一主二从

  • 进入配置目录

1、配置哨兵文件

############哨兵配置文件 sentinel.conf

#创建文件
vim sentinel.conf

#sentinel monitor 被监控的名称 监控主机地址 端口 1(判断主机下线:全部哨兵至少要多少个哨兵同意才算下线)
sentinel monitor myreids 192.168.238.3 6379 1

2、启动哨兵

#bin目录下
redis-sentinel 配置文件/sentinel.conf

如果Master节点断开了,这个时候就会从从机中随机选择一个服务器!(这里面有一个投票算法!)

如果主机恢复了,只能归并到新的主机下,当做从机






Redis缓存

image-20231018195150585



缓存一致性

image-20231018201752223

  • 在业务处,更新数据库的同时更新缓存,可控性更高
  • 防止写操作过多,选择删除缓存,在查询的时候再更新缓存,实现延迟加载,不然每次更新数据库都更新缓存,无效写操作较多
  • 保证原子性:单体系统,将缓存与数据库操作放住一个事务中;分布式系统:使用TCC分布式事务
  • 线程安全问题:

image-20231018203252824

  • 调用者(增删查改)只在缓存中操作,维护一个线程异步将缓存中的操作一次性批处理持久化到数据库中,保存一致性


image-20231018203613186



延迟双删

先删除redis,再更新数据库,延迟N秒后再删除一次redis。






缓存穿透和雪崩

缓存穿透(缓存中没有)

image-20230921235522058


布隆过滤器

缓存空对象

缓存击穿(查询单点高并发)

概念

缓存击穿,是指一个ky非常热点,在不停的扛着大并发大并发集中对这一个点进行访问,当这个ky在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库

当某个ky在过期的瞬间,有大量的请求并发访问,这类数据一般是热点数据,由于缓存过期,会同时访问数据库来查询最新数据,并且回写缓存,会导使数据库瞬间压力过大。


解决方案

1、设置热点数据永不过期

从缓存层面来看,没有设置过期时间,所以不会出现热点ky过期后产生的问题。


2、加互斥锁

分布式锁:使用分布式锁,保证对于每个ky同时只有一个线程去查询后端服务,其他线程没有获得分布式锁的权限,因此只需要等待即可。这种方式将高并发的压力转移到了分布式锁,因此对分布式锁的考验很大。



缓存雪崩

概述

缓存雪崩,是指在某一个时间段,缓存集中过期失效。Redis宕机!

访问查询,都落到了数据库上,对于数据库而言,就会产生周期性的压力波峰。于是所有的请求都会达到存储层,存储层的调用量会暴增,造成存储层也会挂掉的情况。

image-20230922001011947

解决方案:

redisi高可用

既然redis有可能挂掉,那我多增设几台redis,这样一台挂掉之后其他的还可以继续工作,其实就是搭建的集群。(异地多活!)


限流降级

在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个ky只允许一个线程查询数据和写缓存,其他线程等待。


数据预热

数据加热的含义就是在正式部署之前,我先把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中。在即将发生大并发访问前手动触发加载缓存不同的ky,设置不同的过期时间,让缓存失效的时间点尽量均匀。






分布式锁

使用setnx,存在在创建失败,服务进行创建,如果能创建则默认为获取到锁,创建失败则获取锁失败,业务完成需要释放锁(删除key 、设置过期时间)

#分布式锁
set key value NX EX 300 #NX:不存在才创建 EX:过期时间
SET productld:lock 0xx9p03001 NX EX 30000

image-20231019003024883

image-20231019003501259



  • 缺点

image-20231019010954423






Redisson

image-20231019163417520

image-20231019011044469

1、依赖

<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.22.1</version>
</dependency>


2、配置类【推荐】

image-20231019011357462

@Configuration
public class RedisConfig {
@Bean
public RedissonClient redissonClient(){
//配置类
Config config = new Config();
//添加redis地址
config.useSingleServer().setAddress("redis://47.115.222.113:6379");
//创建客户端
return Redisson.create(config);
}
}


3、获取锁 Rlock

多了设置等待时间。实现重试 Rlock 是 使用Hash类型

image-20231019011413188

//获取锁  --- 在redis使用setnx创建锁key,但这是hash类型
RLock lock = redissonClient.getLock("lock:order" + userID);
//尝试获取锁,(获取锁最大等待时间,锁释放时间,时间单位)
boolean isLock = lock.tryLock(1, 30, TimeUnit.SECONDS);
if (isLock){
try {
System.out.println("执行业务");
}finally {
//释放锁
lock.unlock();
}
}


可重入锁

同一线程:线程持有一把锁,但线程可以多次获取同一把锁,而不会被锁本身所阻塞。这是可重入锁的一个主要特性。

  • 可重入原理:判断是不是同一个线程,是的话给锁 ,需要记录当前线程标识和重入次数

image-20231019154306328


  • 加锁

image-20231019154608512

  • 释放锁

image-20231019154645198

Rlock底层就算是这lua脚本,所以我们直接使用RLock即可

private RLock lock;

@BeforeEach
void setUp(){
lock=redissonClient.getLock("order");
}

@Test
void method1(){
//尝试获取锁
boolean isLock = lock.tryLock();
if (!isLock){
log.error("获取锁失败.....1");
return;
}
try {
log.info("获取锁成功......1");
method2();
log.info("开始执行业务.....1");
}finally {
log.warn("准备释放锁.....1");
lock.unlock();
}
}

@Test
void method2(){
//获取锁
boolean isLock = lock.tryLock();
if (!isLock){
log.error("获取锁失败.....2");
return;
}
try {
log.info("获取锁成功......2");
log.info("开始执行业务.....2");
}finally {
log.warn("准备释放锁.....2");
lock.unlock();
}
}





超时释放

  • 使用watchdog,不可以自己设置ttl,使用默认30s,如果ttl到期,业务还没有完成,自动续加30/3=10秒,一直续加到业务完成释放锁就删除


image-20231019163629155






实践

延时任务

image-20230909202359948