Redis6.0学习笔记(入门)
Redis中的key操作
查看当前库中所有的key值
keys *
设置key-value值
set key_name value //返回ok代表成功
查看key是否存在
exists key_name //返回值为1表示存在,0则不存在
查看key的type
type key_name
redis的操作是原子操作,即不会被线程调度机制打断的操作
同时设置一个或多个key-value对
mset key1_name value1 key2_name value2.....
key值不存在的时候设置key的值
setnx key_name value
key值不存在的时候设置一个或多个key的值
msetnx key1_name value1 key2_name value2 key3_name value3.... // 原子性,有一个失败则都失败
设置键值的同时,设置过期时间
setex key_name time value
设置旧值的同时设置新值
getset key_name value
获取key的value值
get key_name
获取一个或多个key-value对
mget key1_name key2_name key3_name...
将给定的值追加到目标key的末尾
append key_name value
获得key值长度
strlen key_name
将key中存储的数字增长1
incr key_name //只能对数字值操作,如果为空,新增值为1
将key中存储的数字增长任意值
incrby key_name value
将key中存储的数字减一
decr key_name
将key中存储的数字减去任意值
decrby key_name value
删除指定key
del key_name //返回值为1表示成功
非阻塞删除
unlink key_name //仅将keys从keyspace元数据中删除,真正的删除会在后续一步操作
设置key过期时间
expire key_name time //以s为单位
查看key的过期时间
ttl key_name // -1代表永不过期 -2代表已经过期
查看当前库的key的数量
dbsize
清空当前库
flushdb
清空所有库
flushall
Redis的基本数据类型
String
String类型是二进制安全的,意味着Redis中的sring可以包含任何数据,比如jpg图片或者序列化对象
一个Redis中字符串valu最多可以是512M
字符串的实际分配空间(capacity)一般会大于他的len,当字符串小于1M的时候,每次扩容都是加倍现有空间
当字符串大于1M的时候,扩容一次只会增加1M的空间,字符串最大为512M
获取key的指定范围的值
getrange key_name start_number end_number
在key的指定位置插入值
setrange key_name start_number value
List
允许重复值
单键多值
底层:双向链表 对两端的操作性能高,中间的操作性能低
数据结构
qickList:链表+ziplist
插入数据
从左边插入一个或多个数据
lpush key_name value1 value2 value3...
例子: lpush k1 v1 v2 v3
lrange k1 0 -1 //代表取所有
(1)v3
(2)v2
(3)v1
从右边插入一个或多个数据
rpush key_name value1 value2 value3...
在list的某一值前或后插入数据
linsert key_name target_value new_value
lpush原理(推箱子):
v1 |
---|
v2 | v1 |
---|
v3 | v2 | v1 |
---|
rpush原理(同理):
v1 |
---|
v1 | v2 |
---|
v1 | v2 | v3 |
---|
获取范围内的数据
从左边取
lrange key_name start_number end_number // lrange k1 0 -1 代表list中的全部value
lindex key_name index //取list中的某一索引的值
llen key_name //获取list长度
弹出数据
rpop/lpop key_name //字面意思从左边或者右边弹出值
rpoppush key1_name key2_name //从key1列表右边吐出一个值,插到key2左边
删除数据
lrem key_name n value // 从左边删除n个value
数据的替换
lset key_name index value //将列表下标为index的值替换为value
Set(集合 )
Set是string类型的无序集合.底层是一个value为null的hash表,所以添加,删除,查找的复杂度都是o(0),单位是menmber(成员)
集合中的值具有唯一性
数据结构
Set的数据结构是dict字典,字典是用哈希表实现的
添加一个或多个数据
sadd key_name value1 value2 value3....
查看集合中所有的数据
smembers key_name
判断集合中是否有某一个值
sismember key_name value
查询集合元素个数
scard key_name
删除集合中的某一元素
srem key_name value1 value2 value3...
在集合中随机弹值
spop key_name
集合中随机取n个值
srandmember key_name n
集合间传值
smove key1_name key2_name key1_value //相同的值会忽略,但是还是会删除
集合间取交集
sinter key1_name key2_name
集合间取并集
sunion key1_name key2_name
集合间取差集
sdiif key1_name key2_name //key1中有的,不包含key2中的
Hash(哈希)
hash是一个string类型的field和value的映射表,hash特别适合用来储存对象如:
key | value | |
---|---|---|
user | field | value |
id | 1 | |
name | 张三 | |
age | 20 |
存储格式
第一种
key value
user: {id:1,name:jack,age:20} 修改太麻烦(不推荐)
第二种
key value
user :id 1
user :name jack
user :age 20
第三种 hash
key value
field value
user id 1
name jack
age 20
存储数据(单个)
hset key_name field_name field_value //给key_name集合中的 field键赋值
存储数据(多个)
hmset key_name field1_name value1 field2_name value2
取出数据
hget key_name field_name
取出key hash集合内所有的field值
hkeys key_name
取出key hash集合内所有的value值
hvals key_name
查看key中的field是否存在
hexists key_name field_name
key 中的hash集合中field加值
hincrby key_name field_name n
key 中hash集合中的field值不存在的时候设置一个field值
hsetnx key_name field_name value
Zset(有序集合)
Redis有序集合zset与普通合集set非常相似,是一个没有重复元素的字符串集合
不同之处在于有序集合中的每一个成员都关联了一个评分,这个评分(score)被用来按照从低到高的方式
排序集合中的成员,集合的成员是唯一的,但是评分可以是重复的
数据结构
(1) hash(存储成员) field value
member_name score
(2)跳跃表(可以快速找到成员)
添加一个或多个成员
zadd key_name score1 value1 score2 value2 score3 value3... //score(评分)
输出范围中的值
zrange key_name start_index end_index [withscores] // 0 -1代表输出所有的值
//输出下标在start_index和end_index之间的元素,添加withscores则会将评分(score)一起输出
zrangebyscore key_name min_score max_score [withscore] [limit offset count]
//返回有序集合key中,所有score值介于min和max之间的(包括min或max)成员
//有序集合按score值从小到大的次序排列
zrevrangescore key_name min_score max_score [withscore] [limit offset count]
//返回有序集合key中,所有score值介于min和max之间的(包括min或max)成员
//有序集合按score值大到小 的次序排列
增加成员的值
zincrby key_name incre_number member_name
删除成员
zrem key_name member_name
统计评分区间的成员个数
zcount key_name min_score max_score
查看成员在集合中的排名
zrank key_name member_name //返回索引(索引从0开始)
Redis 的配置文件
只支持bytes不支持bit
NETWORK
修改配置以网络连接(默认只能本地连接)
bind 127.0.0.1 -::1 (默认本地连接)
// 用#号注释掉即可允许远程连接
protected-mode yes //保护模式(只允许本机连接) 将yes改为no即可支持远程访问
Port
默认6379
tcp-backlog
默认值511
backlog其实是一个连接队列,backlog队列的总和=未完成三次握手队列+已完成三次握手的队列
高并发环境下需要一个高backlog来避免慢客户端的连接问题
tcp-backlog 511
timeout
timeout 0 //客户端未操作指定时间后断开连接 默认值为0(永不过期) 单位为秒
tcp-keepalive
检测客户端的tcp是否活着(操作) 默认每300秒检查一次
tcp-keepalive 300
GENERAL
允许后台启动(默认为no)
daemonize yes
Limits
设置最大的客户端连接数
maxclients 10000 //默认最大连接数10000
设置最大的内存占用量
maxmemory <bytes> //达到最大的内存占用数后根据maxmemory-policy规则进行操作
设置最大内存占用规则
maxmemory-policy
Redis发布和订阅
订阅频道
SUBSCRIBE channel_name // 返回值为订阅人数
向频道发送信息
publish channel_name message
Redis6新数据类型
Bitmap
Bitmap本身不是一种数据类型,它实际上就是字符串(key-value)
但是它可以对字符串进行位操作
通过jedis操作Redis
Jedis jedis = new Jedis(192.168.44.168,6379)
jedis.set()
jedis.get()
jedis.setex()
......
将Redis整合到springboot
具体方法上网百度,大概就是在application.properties中创建redis配置
在java文件夹中创建redis的配置类
使用
//controller
@Autuired
private RedisTemplate redisTemplate
@GetMapping
public String testRedis(){
//设置值到redis
redisTemplate.opsForValue().set("name","lucy")
//从redis中获取值
String name = (String)redisTemplate.opsForValue().get("name")
return name;
}
Redis中的事务操作
Multi、Exec、discard
从输入Multi命令开始,输入的命令一次进入命令队列,但不会执行,知道输入Exec后,Redis会将之前的命令队列中的命令依次执行
组队的时候可以通过discard来放弃组队
--redis客户端
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)>set key1 value1
QUEUED
127.0.0.1:6379(TX)>set key2 value2
QUEUED
127.0.0.1:6379(TX)>exec
1) OK
2) OK
127.0.0.1:6379>
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)>set key1 value1
QUEUED
127.0.0.1:6379(TX)>set key2 value2
QUEUED
127.0.0.1:6379(TX)>discard
OK
127.0.0.1:6379>
事务的错误处理
提交错误
使用exec语句提交时,命令队列无法执行
--redis客户端
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)>set key1 value1
QUEUED
127.0.0.1:6379(TX)>set key2 //语句有语法错误
(error) ERR wrong number of arguments for 'set' command
127.0.0.1:6379(TX)>exec
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379>
执行错误
能正确执行exec提交,但是命令队列中错误的语句报错
--redis客户端
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)>set key1 value1
QUEUED
127.0.0.1:6379(TX)>incr key1 //此时语句语法正确 但是逻辑不正确
QUEUED
127.0.0.1:6379(TX)>set key2 value2
QUEUED
127.0.0.1:6379(TX)>exec
1) OK
2) (erro)ERR value is not an integer or out of range
3) OK
127.0.0.1:6379>
事务冲突问题
悲观🔒
每次拿数据的时候都认为其他事务会修改数据,所以每次拿数据的时候都会上🔒
这样别的事务想拿这个数据就会被block(阻塞)直到它拿到🔒
乐观🔒
在数据上添加版本号,当有事务对数据成功进行更新后同步更新版本号,同时间在进行的事务会将开始事务时
获得的版本号与现在的版本号进行对比,若版本号更新则更新数据后再进行事务的操作
在执行multi之前,先执行watch指令 ,可以监视一个(或多个)key,如果在事务执行之前这个(或这些)key被其他命令所改动
那么事务将会被打断(返回nil)
watch key1 [key2]
事务的三特性
单独隔离操作
事务中的所有命令都会序列化、按顺序的执行.事务在执行的过程中,不会
被其他客户端发送来的命令请求所打断
没有隔离级别
队列中的命令么有提交之前都不会实际被执行,因为事务提交前任何指令都
不会被实际执行
不保证原子性
事务中有一条命令执行失败,其后命令仍然会被执行,没有回滚
并发
测试工具
ab模拟测试
安装:
yum install httpd-tools
通过浏览器测试
ab -n 1000 -c 100 -p ~/postfile -T application/x-www-form-urlencoded http://192.168.137.1:8080/seckill/doseckil
//ab -n 1000 -c 100 代表1000个请求中有100个是并发操作
//-p ~/postfile 代表此目录下的postfile文件
// -p的意思是提交类型为POST ,-T的意思是 content-type的类型
//http://192.168.137.1:8080/seckill 本地的cotroller方法的路径
并发出现的问题
出现秒杀后商品存量为负值(超买超卖)
--乐观锁解决
//监视库存
jedis.watch(kcKey)
//使用事务
Transaction multi = jedis.multi();
//组队操作
multi.decr(kcKey) //kcKey 库存key
multi.sadd(userKey,uid) //秒杀成功的用户uid
并发量太大出现连接超时问题
--使用jedis连接池解决
库存遗留问题(秒杀结束,但是商品未被抢完)
LUA脚本
将复杂的或者多步的redis操作,写为一个脚本,一次提交给redis执行,减少反复连接redis的次数,性能
利用LUA脚本淘汰用户,解决超卖问题
(实际上是redis利用其单线程的特性,用任务队列的方式解决多任务并发问题)
Redis中的持久化操作
Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中
待持久化过程结束了,再用这个临时文件取替换上次持久化好的文件.整个过程中,主进程
是不进行任何IO操作的,这就确保了极高的性能,如果需要进行大规模数据的恢复,且对数据恢复
完整性并不是很敏感,那RDB方式要比AOF方式更加高效.RDB的缺点是最后一次持久化后
数据可能会丢失
dump.rdb
在redis.conf 中的配置文件,默认为dump.rdb
dbfilename dump.rdb
在指定目录生成rdb文件(默认在启动目录生成文件)
dir ./
save
save time num //在time时间(秒)内至少有num个key被改变时执行数据持久化操作
redis无法写入硬盘时停止写入(默认no)
stop-writes-on-bgsave-error yes
对存入到磁盘中的快照是否进行压缩
rdbcompression yes //使用LZF算法进行压缩 但是会消耗CPU性能
检查快照完整性
rdbchecksum yes //开启会有大概10%的数据损耗
RDB(Redis DataBase)
在指定时间间隔内将内存中的数据集快照写入磁盘
AOF(Append only File)
以日志的形式来记录每个写操作(增量保存),将Redis执行过的所有写指令记录下来(读操作不记录)
之追加文件但不改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis重启的话就根据
日志文件的内容将写指令从前到后执行一次完成数据的恢复工作
AOF开启
AOF和RDB同时开启后,系统默认取AOF数据(数据不会存在丢失)
在redis.conf中修改
appendonly no // 默认为no 启动为yes
生成路径
跟RDB生成路径相同
异常恢复
如果遇到AOF文件损坏,通过/usr/loacl!/bin/redis-check-aof–fix appendonly.aof 来进行修复
redis-check-aof --fix appendonly.aof
AOF配置
AOF同步频率设置
appendfsync always
//始终同步,每次Redis的写入都会立刻记入日志;性能较差但是数据完整性较好
appendfsync everysec
//每秒同步,每秒记入日志一次,如果宕机,本秒的数据可能丢失
appendfsync no
redis不主动进行同步,把同步时机交给操作系统
Rewrite压缩
AOF采用文件追加方式,文件会越来越大,为避免出现此种情况,新增了重写机制
当AOF文件超过所设定的阈值时,Redis就会采用AOF文件压缩,只保留可以恢复
数据的最小指令集.可以使用命令bgrewriteaof
auto-aof-rewrite-min-size:设置重写基准值,最小文件为64位.达到这个值后开始重写
重写后达到前一次重写大小的200%后再次重写
系统载入或者上次重写完毕时,Redis会记录此时AOF大小,设为base_size
如果Redis的AOF当前大小>= base_size + base_size*100% 且当前大小>=64mb(默认)的情况下,Redis会对AOF进行重写
持久化操作总结
官方推荐两个都启用
如果对数据不敏感,可以单独用RDB
不建议单独用AOF ,因为会出现bug
如果只是做纯内存缓存,可以都不用
Redis的主从复制
主服务器进行写操作,从服务器只能进行读操作
- 读写分离,性能拓展
- 容灾的快速恢复
-
当从服务器连接到主服务器后,从服务器向主服务器发送进行数据同步的消息
-
主服务器收到消息后对数据进行持久化操作,生成rdb文件,再将rdb文件发送给
从服务器,从服务器拿到rdb文件后进行读取
-
每次主服务器进行写操作之后,就会和服务器进行数据同步
配置
查看主机信息
info replication
在从机上设置主机
slaveof <主机ip> <主机端口号>
//从服务器挂掉后重启并不能自动连接之前的主服务器,而是恢复成默认(将自己认为是主服务器),必须重新设置
//主服务器挂了后重启还是主服务器,他的从服务器仍不会"篡位"
薪火相传
反客为主
大哥挂了小弟立马上位(主服务器挂了,从服务器变成主服务器)
slaveof no one //将从机设置为主机 必须我们在从机手动设置
哨兵模式(反客为主自动版)
反客为主自动版,能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库
规则:
- 优先级:值越小优先级越高
- 偏移量:获得原主机数据最全的
- runid:每个redis实例启动后都会生成一个随机的runid
--在自定义的/myredis目录下新建sentinel.conf文件,名字绝对不能错
--在文件中配置哨兵,填写内容
sentinel monitor mymaster 127.0.0.1 6379 1 // mymaster是为监控对象起的服务器名称,1为至少有多少个哨兵同意迁移的数量
启动
redis-sentinel /myredis/sentinel.conf
从服务器的优先级在redis.conf中默认
slave-priority 100 --值越小优先度越高
Redis集群
容量不够,redis如何进行扩容?
并发操作,redis如何分摊?
解决方法:无中心化集群
代理服务器模式
无中心化集群模式
无中心化集群配置
//redis.conf中进行配置
cluster-enbled yes --打开集群模式
cluster-config-file nodes-6379.conf --设定节点配置文件名,自定义
cluster-node-timeout 15000 设定节点失联时间,超过该时间(毫秒),集群自动进行主从切换
合体
进入redis的主目录下的src目录
redis-cli --cluster create --cluster-replicas 1 192.168.11.101:6379 192.168.11.101:6380 192.168.11.101:6381 192.168.11.101:6389 192.168.11.101:6390 192.168.11.101:6391
--replicacs 1 采用最简单的方式配集群,一台主机,一台从机正好三组
以集群的方式连接Redis
redis-cli -c -p 6379 // -c是以集群的策略连接Redis -p是连接到6379端口(从服务器)
这时候在从服务器中执行写操作,Redis集群会自动切换到集群中的写服务器(主服务器 )
查看集群中的服务器信息
cluster nodes
集群如何分配系节点
一个集群至少要有三个主节点
选项–cluster-reolicas 1表示我们希望为集群中的每一个主节点创建一个从节点
插槽(slot)
一个Redis集群中包含16384个插槽,数据库中的每个键都属于这16384个插槽的其中一个
集群使用CRC16(key)%16384来计算键key属于哪个槽,其中CRC16(key)语句用于计算键
key的CRC16的校验和
集群中的每个节点负责处理一部分插槽.举个例子,如果一个集群可以有主节点,其中:
节点A负责处理0号至5460号插槽
节点B负责处理5461号至10922号插槽
节点C负责处理10923号至16384号插槽
在集群中设置多个值
> mset name lucy age 20 address china
>(error) CROSSSLOT Keys in request don't hash to the same slot
> mset name{user} lucy age{user} 20 address(user) china //为这些值设置一个共同的组
查询集群中的值
cluster keyslot key_name
返回值为插槽值
故障恢复
//redis.conf
cluster-require-full-coverage yes/no --yes(如果某段插槽的主从服务器全部挂掉,那么整个集群都挂掉)
--no(如果某一段插槽的主从都挂掉,那么该插槽数据全部不能使用,也无法存储)
集群的Jedis开发
HostAndPort hostAndPort = new HostAndPort("集群中任意服务器的IP地址",集群中任意服务器的端口号)
JedisCluster jediscluster = new Jediscluster(hostAndPort)
jedisCluster.set("b1","value1") //设置值
String value = jedisCluster.get("b1") //取值
jedisCluster.close()
Redis应用问题的解决
缓存穿透
现象
- 应用服务器压力突然变大
- redis命中率降低
- 一直查询数据库
缓存中查询不到数据,redis一直查询数据库,导致数据库独自承压,最终导致数据库崩溃
- redis查询不到数据库
- 出现很多非正常的url访问
解决方案
1.对空值进行缓存
2.设置可访问的名单(白名单)
3.采用布隆过滤器
缓存击穿
现象
-
数据库访问压力瞬时增大
-
redis中并没有大量key过期
-
redis正常运行
-
redis某个key过期了,但是有大量的访问使用这个key
解决方案
1.预先设置热门数据:在redis高峰访问之前,把一些热门数据提前存入到redis里面,加大热门数据key时长
2.实时调整:现场监控哪些数据热门,实时调整key的过期时长
3.使用锁
缓存雪崩
现象
- 在极少的时间段,查询大量key的集中过期
解决方法
1.构建多级缓存框架:nginx缓存+redis缓存+其他缓存(ehcache等)
2.使用锁或者队列
3.设置过期标志更新缓存(记录缓存数据是否过期(设置为提前量),如果过期会触发通知另外的线程去更新实际的key的缓存)
4.将缓存失效的时间分散开:通过随机数设置过期时间
分布式锁(共享锁)
单传的java API 并不能提供分布式锁的能力.为了解决这个问题就需要一种跨
JVM的互斥机制来控制共享资源的访问
分布式锁的主流实现方法:
- 基于数据库实现分布式锁
- 基于缓存(redis等)
- 基于Zookeeper
每一种分布式锁解决方案都有各自的优缺点:
- 性能:redis最高
- 可靠性;Zookeeper最高
基于redis实现分布式锁
--设置值的时候上锁
setnx key_name value
--解锁
del key_name (删除key时解锁)
--上锁的时候同时设置过期时间(时间过期后锁自动解除)
set key_name value nx ex 12 (nx代表锁,ex后接过期时间(s ))
Redis6的新功能
ACL(访问控制列表)
将用户的权限进行更细粒度的权限控制
(1),接入权限:用户名,密码
(2)用户可执行的命令
(3)用户可以操作的key
命令
acl list //展现当前所有用户的信息
acl cat //查看具体操作的指令列表
acl setuser user_name //添加用户以及权限
acl whoami //查看当前用户名