- 1. NoSQL概述
- 2. Redis概念介绍
- 3. Redis数据类型
- 4. Redis命令
- 5. Redis事务
- 6. Jedis操作Redis
- 7. springboot整合redis
- 8. Redis.conf详解
- 9. Redis持久化
- 10. Redis订阅发布
- 11. Redis主从复制
- 12. Redis缓存穿透和雪崩
- NoSQL = Not only SQL。泛指非关系型数据库
- NoSQL特点
- 方便扩展(数据之间没有关系,很好扩展)
- 大数据量高性能(NoSQL的缓存记录级,是一种细粒度的缓存,性能会比较高!)
- 数据类型是多样型的
- 概述
- Reids(Remote Dictionary Server),即远程字典服务!是一种开源的使用ANSI C语言编写,支持网络,可基于内存亦可持久化的日志型,Key-Value数据库,并提供多种语言的API。
- 作用
- 内存存储,持久化,内存中是断电即失,所以说持久化很重要
- 效率高,可以用于高速缓存
- 发布订阅系统
- 地图信息分析
- 计时器,计数器(浏览量!)
- …
- 特性
- 多样的数据类型
- 持久化
- 集群
- 事务
- …
- 默认端口号:6379
- 默认有16个数据库,利用select命令可以进行切换到不同数据库
3. Redis数据类型
- Redis是单线程的
- Sting(字符串)
set key1 v1 # 设置值. # v1
get key1 # 获得值.
append key1 "hello" # 后边追加hello. # v1hello
STRLEN key1 # 字符串长度. # 7
set views 0
incr views # 值加1. # 1
decr views # 值减1. # 0
INCRBY views 10 # 值加10 # 10
DECRBY views 5 # 值减5 # 5
set key2 "hello,zhangsan"
GETRANGE key2 0 3 # 截取字符串 # hell
GETRANGE key2 0 -1 # 获取全部字符串 # hello,zhangsan
set key3 "abcdefg"
SETRANGE key3 1 xx # 从1位置开始替换内容 # axxdefg
setex key4 30 "hello" # 设置key4的值为hello, 并且30秒后过期
setnx myKey redis # 如果不存在则设置 # redis
setnx myKey redis_change # redis
mset k1 v1 k2 v2 k3 v3 # 一次性设置多个值.
mget k1 k2 k3 # 批量获取 # v1 v2 v3
set user:1 {name:lisi,age:23} #设置一个user1对象
mset user:2:name zhangsan user:2:age 24
mget user:2:name user:2:age
getset db re # 先获取再设置 # re
getset db re_change # 输出re 重设置为re_change
- List类型
LPUSH list one # 将一个值或者多个值,插入到列表头部(左) LPUSH list two LPUSH list three LRANGE list 0 -1 # 获取所有值 # three two one LRANGE list 0 1 # 获取0-1的值 # three two Rpush list righr # 将一个值或者多个值,插入到列表尾部(右) LRANGE list 0 -1 # three two one righr Lpop list # 移除左边第一个值 # three Rpop # 移除右边第一个值 # righr LRANGE list 0 -1 # two one Lindex list 1 # 通过下标获取下标为1处的值 # one Lindex list 0 # 通过下标获取下标为0处的值 # two Llen list # 获取列表长度 # 2 Lrem list 1 one # 移除list中为one的值,移除1个 # two Rpush myList "hello" Rpush myList "hello1" Rpush myList "hello2" Rpush myList "hello3" ltrim myList 1 2 #截断,只保留1,2位置的值. # hello1, hello2 rpoplpush myList myotherlist # hello2 lrange myList 0 -1 # hello1 lrange myotherlist # hello2 lset myList 0 hello_0 #将myList指定位置的值进行更改 # hello_0 Rpush mLi "hello" Rpush mLi "world" LINSERT mLi before "word" "other" # 在word的前边插入other # hello other world LINSERT mLi after world new # 在word后边插入new # hello other world new
- set类型
sadd myset "hello" sadd myset "zhangsan" sadd myset "nihao" SMEMBERS myset # 查看值 SISMEMBER myset hello # 判断某一个值是不是在set集合中 scard myset # 获取set集合中的元素个数 # 3 srem myset hello # 移除hello元素 SRANDMEMBER myset # 随机抽选出一个元素 spop myset # 随机删除一些set集合中的元素 sadd mset "hello" sadd mset "world" sadd mset "zhangsan" sadd mset2 "set2" smove mset mset2 "zhangsan" # 将mset中的zhangsan 移动到 mset2中 SMEMBERS mset # world, hello SMEMBERS mset2 # zhangsan set2 sadd key1 a sadd key1 b sadd key1 c sadd key2 c sadd key2 d sadd key2 e SDIFF key1 key2 # 计算差集 # b, a SINTER key1 key2 # 计算交集 # c SUNIOn key1 key2 # 计算并集 # b,c,e,a,d
- Hash类型
hset myhash field1 zhangsan # 存放field1-zhangsan hget myhash field1 # zhangsan hmset myhash field1 hello field2 world # 存放多个 hmget myhash field1 field2 # hello, world hgetall myhash # 获取全部的数据 # field1,hello, field2,world hdel myhash field1 # 删除hash指定key字段,对应的value值也会消失 hlen myhash # 查看长度 # 1 hkeys myhash # 获取所有的field # field2 hvals myhash # 获取所有的value # world hset myhash field3 5 HINCRBY myhash field3 1 # 值增加1 # 6 hsetnx myhash field4 hello # 如果不存在则可以设置 hsetnx myhash field4 world # 如果存在则不能设置
- Zset(有序集合):在set的基础上,增加了一个值,set k1 v1 ; zset k1 score1 v1
zadd myset 1 one zadd myset 2 two zadd myset 3 three ZRANGE myset 0 -1 # 获取所有的值 # one, two, three zadd salary 2500 zhangsan zadd salary 5000 lisi zadd salary 500 wangwu ZRANGEBYSCORE salary -inf +inf # 按照salary进行从小到大排序 # wangwu,zhangsan,lisi ZREVRANGE salary 0 -1 # 按照从大到小进行排序 ZRANGEBYSCORE salary -inf +inf withscores # wangwu, 500, zhangsan, 2500, lisi, 5000 ZRANGEBYSCORE salary -inf 2500 # wangwu,zhangsan zrange salary 0 -1 zrem salary zhangsan # 移除元素 # lisi,wangwu zcard salary # 显示元素数量 # 24. Redis命令
- 切换数据库:select _
- 清除当前数据库:flushdb
- 清除全部数据库内容:FLUSHALL
- 查看所有的数据:keys *
- 添加数据:set name value
- 查看某个数据是否存在:EXISTS name
- 移除数据:move name 1,1表示当前数据库
- 更新值:set name zhangsan
- 设置值过期时间:EXPIRE name 10
- 查看数据类型:type name
- Redis单条命令是保存原子性的,但是事务不保证原子性!
- Redis事务本质:一组命令的集合!一个事务中的所有命令都会被序列化,在事务执行的过程中,会按照顺序执行!一次性、顺序性、排他性!执行一些列的命令!
- Redis事务没有隔离级别的概念!
- redis事务
- 开启事务(multi)
- 命令入队(…)
- 执行事务(exec)
multi # 开启事务 set k1 v1 # 命令入队 set k2 v2 get k2 set k3 v3 exec # 开始执行 # OK, OK, v2, OK multi # 开启事务 set k1 v1 # 命令入队 set k2 v2 get k2 set k3 v3 DISCARD # 放弃事务 # 上边的命令都不会执行了
- 编译型异常(代码有问题!命令有错!),事务中所有的命令都不会被执行!
- 运行时异常(1/0),如果事务队列中存在语法性,那么执行命令的时候,其他命令是可以正常执行的,错误命令抛出异常!
- 乐观锁:使用watch实现
# 线程1 set money 100 set out 0 watch money # 监视money属性 multi DECRBY money 20 INCRBY out 20 exec # 执行之前线程2修改了值会导致这个事务执行失败 # 线程2 get money set money 10006. Jedis操作Redis
- 概念
- Jedis是Redis官方推荐的java连接开发工具!使用Java操作Redis中间件!
- 使用步骤
- 导入依赖
redis.clients jedis 3.7.0 com.alibaba fastjson 1.2.78
- 编码测试
public class Ping {
public static void main(String[] args) {
// 1. new Jedis对象
Jedis jedis = new Jedis("127.0.0.1", 6379);
// 2. jdeis的指令就是上边的所有指令
System.out.println(jedis.ping());
JSONObject jsonObject = new JSONObject();
jsonObject.put("hello", "world");
jsonObject.put("name", "zhangsan");
String res = jsonObject.toJSONString();
//开启事务
Transaction multi = jedis.multi();
try{
multi.set("user1", res);
multi.set("user2", res);
multi.exec(); // 执行事务
}catch(Exception e) {
multi.discard(); //放弃事务
e.printStackTrace();
}finally{
System.out.println(jedis.get("user1"));
System.out.println(jedis.get("user2"));
// 3. 关闭连接
jedis.close();
}
}
}
7. springboot整合redis
- 导入依赖
org.springframework.boot spring-boot-starter-data-redis
- 配置redis
# 配置redis spring.redis.host=127.0.0.1 spring.redis.port=6379
- 连接测试
@SpringBootTest
class RedisTest{
@Autowired
private RedisTemplate redisTemplate;
@Test
void contextLoads(){
//opsForValue:操作字符串,类似于String
//opsForList:操作List,类似于List
//opsForxxx:......
redisTemplate.opsForValue().set("key", "value");
//获取redis的连接对象
RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
}
}
8. Redis.conf详解
- 网络配置
bind 127.0.0.1 # 绑定的ip protected-mode yes # 保护模式 port 6379 # 端口设置
- 通用配置
daemonize yes # 以守护进程的方式运行,默认是no,我们需要自己开启为yes! pidfile /var/run/redis_6379.pid # 如果以后台方式运行,我们就需要指定一个pid文件 # 日志 # debug # verbose # notice # warning loglevel notice logfile "" # 日志的文件位置名 databases 16 # 数据库的数量,默认是16个数据库 always-show-logo yes # 是否总是显示LOGO
- 快照(持久化)
- 持久化,在规定的时间内,执行了多少次操纵,则会持久化到文件.rdb.aof
# 如果900秒内,如果至少有一个key进行了修改,我们就进行持久化操作 save 900 1 # 如果300秒内,如果至少10key进行了修改,我们就进行持久化操作 save 300 10 # 如果60秒内,如果至少10000key进行了修改,我们就进行持久化操作 save 60 10000 stop-writes-on-bgsave-error yes # 持久化如果出错,是否还需要继续工作! rdbcompression yes # 是否压缩rbd文件,需要消费一些cpu资源! rdbchecksum yes # 保存rbd文件的时候,进行错误的检查校验!
- SECURITY安全
可以在这里设备redis的密码,默认是没有密码!
config get requirepass # 获取redis的密码 config set requirepass "123456" # 设置redis的密码 config get requirepass # 所有命令没有权限 auth 123456 # 利用密码登录 config get requirepass # 命令就可以用了
- 限制CLIENTS
maxclients 10000 # 设置能连接上redis的最大客户端的数量 maxmemory9. Redis持久化# redis配置最大的内存容量 maxmemory-policy noeviction # 内存到达上限之后的处理策略
RDB(Redis Database)
- RDB保存
- rdb保存的文件是dump.rdb
- 在配置文件中快照中进行配置
- RDB触发机制
- save的规则满足的情况下,会自动触发rdb规则
- 执行flushall命令,也会触发rdb规则
- 退出redis,也会产生rdb文件
- 恢复rdb文件
- 只需要将rdb文件放在我们redis启动目录就可以了,redis启动的时候会自动检查dump.rdb恢复其中的数据!
- 查看需要存在的位置
config get dir # "dir" # "/usr/local/bin" # 如果在这个目录下存在dump.rdb文件,启动就会自动恢复其中的数据
- RDB的优点和缺点
- 优点:
- 适合大规模的数据恢复
- 对数据的完整性要求不高
- 缺点:
- 需要一定的时间间隔进程操作!如果redis意外宕机了,这个最后一次修改数据就没有的了。
AOF(Append only File)
- AOF保存文件
- Aof保存的是appendonly.aof文件
- 将我们所有的命令都记录下来,history,恢复的时候就把这个文件全部再执行一遍
- append
- 默认是不开启的,需要手动配置。将appendonly改为yes
appendonly no # 默认是不开aof模式的,默认是使用rdb方式持久化的 appendfilename "appendonly.aof" # 持久化的文件的名字 # appendfsync always # 每次修改都会sync.消耗性能 appendfsync everysec # 每秒执行一次sync,可能会丢失这1s的数据! appendfsync no # 不执行sync,这个时候操作系统自己会同步数据,速度最快!
- 优点和缺点
- 优点
- 每一次修改都同步,文件的完整会更加好!
- 每秒同步一次,可能会丢失一秒数据
- 从不同步,效率最高
- 缺点
- 相对于数据文件来说,aof远远大于rdb,修复的速度也比rdb慢
- aof运行效率比rdb慢,所以redis默认的配置就是rdb持久化
- 概述
- Redis的发布订阅是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息
- 使用
# 线程1:开启订阅,接收消息 SUBSCRIBE fanhuabiji # hello world # 线程2:发布消息 PUBLISH fanhuabiji "hello world"
- 原理
- 通过SUBSCRIBE命令订阅某频道后,redis-server维护了一个字典,字典的键就是一个个的频道,字典的值就是一个链表,链表中保存了所有订阅这个频道的客户端。SUBSCRIBE命令的关键,就是将客户端添加到给定频道的订阅链表中。
- 通过PUBLISH命令向订阅发送消息,redis-server会使用给定的频道作为键,在它所维护的频道字典中查找记录了订阅这个频道的所有客户端的链表,遍历这个链表,将消息发布给订阅者。
- 概念
- 主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(master),后者称为从节点(slave);数据的复制是单向的,只能由主节点到从节点。
- 默认情况下,每台Redis服务器都是主节点
- 作用
- 数据冗余:是持久化以外的一种数据冗余方式
- 故障恢复:主节点出现问题,可以由从节点提供服务
- 负载均衡:读写分析
- 高可用基石:主从复制还是哨兵和集群能够实施的基础
- 环境配置
info replication # 查看当前库的信息
- 复制3个配置文件,然后修改对应的信息
- 端口
- pid名字
- log文件名字
- dump.rdb名字
- 利用命令配置主从复制【暂时的】
# 配置一主二从 6379主,6380,6381从 # 6379服务端输入 SLAVEOF 127.0.0.1 6379 # 6380认6379为主 # 6381服务端输入 SLAVEOF 127.0.0.1 6379
- 在配置文件中配置主从复制【永久的】
# redis.conf中修改就行了
- 测试:主机断开连接,从机依旧连接到主机的,但是没有写操作,这个时候,主机如果回来了,从机依旧可以直接获取到主机写的信息!
- 如果是使用命令行来配置的主从,这个时候如果重启了,就会变为主机!只要变为从机,立马就会从主机获得值!
- 如果主机断开了连接,可以使用SLAVEOF no one让自己变成主机!【手动方式】
- 哨兵模式【自动选取主机】
- 后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库
- 哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独立运行。其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。
# 哨兵配置 # 1. 配置哨兵配置文件 sentinel.conf # sentinel monitor 被监控的名称 host port 1 # 后面的1,代表了主机挂了,slave投票看让谁接替成为主机 sentinel monitor myredis 127.0.0.1 6379 1 # 2. 启动哨兵 redis-sentinel sentinel.conf
- 主机宕机后会重新选新主机,如果之后主机回来了,只能归并到新的主机下,当作从机,这就是哨兵模式的规则!
- 优缺点
- 优点
- 哨兵集群,基于主从复制模式,所有的主从配置优点,它全有
- 主从可以切换,故障可以转移,系统的可用性就会更好哨兵模式就是主从模式的升级,手动到自动,更加健壮
- 缺点
- Redis不好在线扩容,集群容量一旦到达上限,在线扩容就十分麻烦
- 实现哨兵模式的配置很麻烦
- 优点
- 缓存穿透【查不到】
- 概念:用户想要查询一个数据,发现redis内存数据库没有,也就是缓存没有命中,于是向持久层数据库查询。发现也没有,于是本次查询失败。当用户很多的时候,缓存都没有命中,于是都去请求了持久层数据库。这会给持久层数据库造成很大的压力,这时候就相当于出现了缓存穿透。
- 解决方案1:布隆过滤器。布隆过滤器是一种数据结构,对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则丢弃,从而避免了对底层存储系统的查询压力;
- 解决方案2:当存储层不命中后,即使返回的空对象也将其缓存起来,同时会设置一个过期时间,之后再访问这个数据将会从缓存中获取,保护了后端数据源。
- 缓存击穿【量太大】
- 概念:缓存击穿是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像一个屏幕上凿开了一个洞。
- 解决方案1:设置热点数据永不过期
- 解决方案2:加互斥锁。分布式锁:使用分布式锁,保证对于每个key同时只有一个线程去查询后端服务,其他线程没有获得分布式锁的权限,因此只需要等待即可。这种方式将高并发的压力转移到了分布式锁,因此对分布式锁的考验很大。
- 缓存雪崩
- 概念:在某一个时间段,缓存集中过期失效。Redis宕机!
- 产生原因之一:在写文本的时候,马上就要到双十二零点,很快就会迎来一波抢购,这波商品时间比较集中的放入缓存,假设缓存一个小时。那么到了凌晨一点钟的时候,这批商品的缓存就都过期了。而对这批商品的访问查询,都落到了数据库上,对于数据库而言,就会产生周期性的压力波峰。于是所有的请求都会到达过存储层,存储层的调用量会暴增,造成存储层也会挂掉的情况。



