技术的分类:
NoSQL(NoSQL = Not Only SQL),意思是“不仅仅是SQL”,泛指非关系型数据库。NoSQL不依赖业务逻辑方式存储,而以简单的key-value模式存储。因此大大的增加了数据库的扩展能力。
Redis
MongoDB
开源
的key-value
存储系统string
(字符串)、list
(链表)、set
(集合)、zset
(sorted set-- 有序集合)和hash
(哈希类型)原子性
的排序
缓存在内存
中周期性
的把更新的数据写入磁盘
或者把修改操作写入追加的记录文件master-slave(主从)
同步到http://redis.io.com下载redis的压缩文件包
用Xftp上传到Linux操作系统上的opt目录
查看当前LInux系统是否安装了gcc编译器
gcc --version;
进入opt目录
cd /
cd /opt/
解压redis压缩文件
tar -zxvf redis-6.2.7.tar.gz
进入redis-6.2.7目录编译redis文件
cd redis-6.2.7/
make
安装
make install
查看默认安装目录:
redis-benchmark:性能测试工具
redis-check-aof:修复有问题的AOF文件,rdb和aof后面讲
redis-check-dump:修复有问题的dump.rdb文件
redis-sentinel:Redis集群使用
redis-server:Redis服务器启动命令
redis-cli:客户端,操作入口
前台启动(不推荐)
#启动命令
[root@bogon bin]# redis-server
7476:C 27 Oct 2022 09:33:23.040 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
7476:C 27 Oct 2022 09:33:23.040 # Redis version=6.2.7, bits=64, commit=00000000, modified=0, pid=7476, just started
7476:C 27 Oct 2022 09:33:23.040 # Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf
7476:M 27 Oct 2022 09:33:23.041 * Increased maximum number of open files to 10032 (it was originally set to 1024).
7476:M 27 Oct 2022 09:33:23.041 * monotonic clock: POSIX clock_gettime
7476:M 27 Oct 2022 09:33:23.042 # A key '__redis__compare_helper' was added to Lua globals which is not on the globals allow list nor listed on the deny list.
_._
_.-``__ ''-._
_.-`` `. `_. ''-._ Redis 6.2.7 (00000000/0) 64 bit
.-`` .-```. ```\/ _.,_ ''-._
( ' , .-` | `, ) Running in standalone mode
|`-._`-...-` __...-.``-._|'` _.-'| Port: 6379
| `-._ `._ / _.-' | PID: 7476
`-._ `-._ `-./ _.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' | https://redis.io
`-._ `-._`-.__.-'_.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' |
`-._ `-._`-.__.-'_.-' _.-'
`-._ `-.__.-' _.-'
`-._ _.-'
`-.__.-'
7476:M 27 Oct 2022 09:33:23.042 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
7476:M 27 Oct 2022 09:33:23.042 # Server initialized
7476:M 27 Oct 2022 09:33:23.042 # WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect.
7476:M 27 Oct 2022 09:33:23.042 * Ready to accept connections
后台启动
拷贝一份redis.conf到其他目录
cp /opt/redis-6.2.7/redis.conf /etc/redis.conf
将后台启动设置daemonize no改为yes
即:将redis.conf文件将里面的daemonize no改为yes,让如无在后台启动
vim redis.conf
进入/usr/local/bin/目录,重新启动redis
cd /usr/local/bin/
#使用指定的配置文件启动
redis-server /etc/redis.conf
查看redis的进程信息
[root@bogon bin]# ps -ef | grep redis
root 7679 1 0 09:49 ? 00:00:00 redis-server 127.0.0.1:6379
root 7685 2829 0 09:49 pts/0 00:00:00 grep --color=auto redis
测试访问
#用客户端访问
[root@bogon bin]# redis-cli
#测试验证
127.0.0.1:6379> ping
PONG
127.0.0.1:6379>
Redis关闭
单实例关闭
redis-cli shutdown
也可以进入终端后再关闭
127.0.0.1:6379>shutdown
not connected>
多实例关闭,指定端口号再关闭
redis-cli -p 6379 shutdown
用Linux的kill命令杀死对应的进程号也可以
#-9强制删除
[root@linuxcool ~]# kill -9 1518
端口号:6379
默认16个库,类似数组下标从0开始,初始默认使用0号库
使用命令select 来切换数据库。如:select 8
统一密码管理,所有数据库同样密码
dbsize查看当前数据库的key的数量
flushdb清空当前库
flushall通杀全部库
Redis是单线程+多路IO复用技术
多路复用是指用一个线程来检查多个文件描述符(Socket)的就绪状态,比如调用select的poll函数,传入多个文件描述符,如果有一个文件描述符就绪,则返回,否则阻塞直到超时。得到就绪状态后进入真正的操作可以在同一个线程里执行,也可以启动线程执行(比如使用线程池)
哪里区获得redis常用数据类型操作命令:http://www.redis.cn/commands.html
#插入一些键
set key1 jack
set key2 lucy
set key3 john
keys * 查看当前库所有key
127.0.0.1:6379> keys *
1) "key2"
2) "key3"
3) "key1"
exists key:判断某个key是否存在
127.0.0.1:6379> exists key1
(integer) 1
127.0.0.1:6379> exists key4
(integer) 0
type key:查看你的key是什么类型的
127.0.0.1:6379> type key1
string
del key:删除指定的key数据
127.0.0.1:6379> del key1
(integer) 1
unlink key:根据value选择非阻塞删除(异步删除)
#仅将keys从keyspace元数据中删除,真正的删除会在后续异步操作
127.0.0.1:6379> unlink key2
(integer) 1
expire key 10 :为给定的key设置过期时间
127.0.0.1:6379> expire key1 5
(integer) 1
ttl key:查看还有多少秒过期,-1表示永不过期,-2表示已经过期
127.0.0.1:6379> ttl key1
(integer) -2
select 命令切换数据库
127.0.0.1:6379> select 1
OK
127.0.0.1:6379[1]>
dbsize查看当前数据库的key的数量
127.0.0.1:6379> dbsize
(integer) 3
flushdb 清空当前库
127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> dbsize
(integer) 0
1.简介
String是Redis最基本的类型,你可以理解成与Memcached一模一样的类型,一个key对应一个value
String类型时二进制安全
的。意味着Redis的string可以包含任何数据比如jpg图片或者序列化的对象
String类型是Redis最基本的数据类型,一个Redis中字符串value最多可以是512M
2.命令
set 添加键值对
> set key value [EX seconds | PX milliseconds | KEEPTTL] [NX | XX]
get 查询对应的键值
append 将给定的追加到原值的末尾
strlen 获取值的长度
setnx 只有在key不存在时,设置key值
incr
将key中存储的数字值增1
只能对数字值操作,如果为空,新增值为1
127.0.0.1:6379> set k2 1
OK
#类似i++
127.0.0.1:6379> incr k2
(integer) 2
127.0.0.1:6379> get k2
"2"
decr
incrby / decrby <步长>将key中存储的数字值增减。自定义步长,可以不是1
原子性
所谓原子操作时指不会被线程调度机制打断的操作
这种操作一旦开始,就一直运行到结束,中间不会有任何context Switch(切换到另一个线程)
- 在单线程中,能够在单条指令中完成的操作都可以认为是“原子操作”,因为中断只能发生于指令之间
- 在多线程中,不能被其他进程(线程)打断的操作就叫做原子操作
Redis单命令的原子性主要得益于Redis的单线程
mset …
同时设置一个或多个key-value对
127.0.0.1:6379> mset k1 val1 k2 val2 k3 val3
OK
127.0.0.1:6379> keys *
1) "k3"
2) "k2"
3) "k1"
mget
同时获取一个或多个value
127.0.0.1:6379> mget k1 k2 k3
1) "val1"
2) "val2"
3) "val3"
msetnx < value2>…
同时设置一个或多个key-value对,当且仅当所有 给定的key都 不存在
原子性,有一个失败都失败
127.0.0.1:6379> msetnx k4 val4 k5 val5
(integer) 1
127.0.0.1:6379> keys *
1) "k2"
2) "k1"
3) "k3"
4) "k5"
5) "k4"
getrange <起始位置> <结束位置>
获取值得范围,类似于java中的substring,前闭后开
127.0.0.1:6379> get key1
"jack and rose"
127.0.0.1:6379> getrange key1 0 4
"jack "
setrange <起始位置> < value>
用覆盖所存储的字符串的值,从<起始位置>开始(索引从0开始)
127.0.0.1:6379> get k1
"jack"
127.0.0.1:6379> setrange k1 3 rose
(integer) 7
127.0.0.1:6379> get k1
"jacrose"
setex 过期时间
设置键值的同时,设置过期时间,单位:秒
127.0.0.1:6379> setex k1 10 val
OK
127.0.0.1:6379> ttl k1
(integer) 7
127.0.0.1:6379> ttl k1
(integer) 6
getset
以旧换新,设置了新值的同时获得旧值
127.0.0.1:6379> getset k2 value2
"val2"
127.0.0.1:6379> get k2
"value2"
底层数据结构:
String的数据结构为简单动态字符串(Simple Dynamic String,缩写为SDS)。是可以修改的字符串,内部结构实现上类似于Java的ArrayList,采用预分配冗余空间的方式来减少内存的频繁分配。
简介
单键多值
Redis列表是简单的字符串列表,按照插入顺序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)。
它的底层是一个双向链表,对两端的操作性能很高,通过索引下标的操作中间的结点性能会较差。
常用命令
lpush / rpush …
从左边/右边插入一个或多个值
127.0.0.1:6379> lpush key1 v1 v2 v3 v4
(integer) 4
127.0.0.1:6379> lrange key1 0 -1
1) "v4"
2) "v3"
3) "v2"
4) "v1"
lpop / rpop
从左边/右边吐出一个值。值在键在,值光键亡
127.0.0.1:6379> rpop key1
"v1"
rpop lpush
从列表右边吐出一个值,插入到列表的左边
127.0.0.1:6379> rpush key1 rightvalue1
(integer) 4
lrange
按照索引下标获取元素(从左到右)
127.0.0.1:6379> lrange key1 0 -1
1) "v4"
2) "v3"
3) "v2"
4) "rightvalue1"
lindex
按照索引下标获取元素(从左到右)
127.0.0.1:6379> lindex key1 0
"v4"
llen
获得列表长度
127.0.0.1:6379> llen key1
(integer) 4
linsert before
在的后面插入插入值
127.0.0.1:6379> linsert key1 after rightvalue1 hello
(integer) 5
lrem
从左边删除n个value(从左到右)
127.0.0.1:6379> lrange key1 0 -1
1) "v3"
2) "v2"
3) "rightvalue1"
4) "hello"
127.0.0.1:6379> lrem key1 1 v3
(integer) 1
127.0.0.1:6379> lrange key1 0 -1
1) "v2"
2) "rightvalue1"
3) "hello"
lset
将列表key下标为index的值替换为value
127.0.0.1:6379> lset key1 0 v0
OK
127.0.0.1:6379> lindex key1 0
"v0"
数据结构
List的数据结构为快速链表quickList
首先在列表元素较少的情况下会使用一块连续的内存存储,这个结构是ziplist,也就是压缩列表。
它将所有的元素紧挨着一起存储,分配的是一块连续的内存。
当数据量比较多时才会改成quicklist
因为普通链表需要附加指针空间太大,会比较浪费空间
Redis将链表和ziplist结合起来组成了quicklist。也就是将多个ziplist使用双向指针串起来使用。这样既满足了快速的插入删除性能,又不会出现太大的空间冗余。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DWWiVlY8-1683813026084)(C:\Users\12086\AppData\Roaming\Typora\typora-user-images\image-20221028105241342.png)]
简介
Redis set对外提供了的功能与list类似是一个列表的功能,特殊之处在于set是可以自动排重的,当你需要存储一个列表数据,又不希望出现重复数据时,set是一个很好的选择。并且set提供了判断某个成员是否在一个set集合内的重要接口,这个也是list不能提供的。
Redis的set是string类型的无序集合,它底层其实是一个value为null的hash表,所以添加、删除、查找的复杂度都是O(1)
一个算法,随着数据的增加,执行时间的长短,如果是O(1),数据增加,查找数据的时间不变
常用命令
sadd
将一个或多个member元素加入集合key中,已经存在的member元素将会忽略
127.0.0.1:6379> sadd set1 v1 v2 v3 v4
(integer) 4
smembers
取出该集合的所有值
127.0.0.1:6379> smembers set1
1) "v2"
2) "v1"
3) "v4"
4) "v3"
sismember
判断集合key中是否含有value值,有返回1,没有0
127.0.0.1:6379> sismember set1 v1
(integer) 1
127.0.0.1:6379> sismember set1 v5
(integer) 0
scard 返回该集合的元素个数
127.0.0.1:6379> scard set1
(integer) 4
删除集合中的某个元素
127.0.0.1:6379> srem set1 v1
(integer) 1
127.0.0.1:6379> smembers set1
1) "v2"
2) "v4"
3) "v3"
随机从该集合吐出一个值
127.0.0.1:6379> spop set1 1
1) "v4"
随机从该集合取出n个元素,但并不会删除元素
127.0.0.1:6379> srandmember set1 3
1) "v2"
2) "v3"
smove value把集合中一个值移动到另一个集合
sinter 返回两个集合的交集元素
sunion 返回两个集合的并集元素
sdiff 返回两个集合的差集元素(key1中的,不包含key‘2)
数据结构
Set数据结构是dict字典,字典是用哈希表实现的
Java中的HashSet的内部实现使用的是HashMap,只不过所有的value都指向同一个对象。Redis的set结构也是一样,它的内部也使用Hash结构,所有的value都指向同一个内部值
简介
Redis hash 是一个键值对集合
Redis hash是一个string类型的field和value的映射表,hash特别适合用于存储对象。
类似于Java里面的Map
常用命令
hset
给key集合中的field键赋值value
127.0.0.1:6379> hset user:1001 id 1
(integer) 1
hget
从集合key1的field中取出value
127.0.0.1:6379> hget user:1001 id
"1"
hmset < value1> …
批量设置hash值
127.0.0.1:6379> hmset user:1002 id 1 name zhangsan
OK
127.0.0.1:6379> hget user:1002 name
"zhangsan"
hexists
查看哈希表key中,给定field是否存在
127.0.0.1:6379> hexists user:1002 id
(integer) 1
hkeys
列出该hash集合的所有field
127.0.0.1:6379> hkeys user:1002
1) "id"
2) "name"
hvals 列出该hash集合的所有value
127.0.0.1:6379> hvals user:1002
1) "1"
2) "zhangsan"
hincrby 为哈希表的域field的值加上常量1
hsetnx
将哈希表key中的域field的值设置为value,当且仅当域field不存在
127.0.0.1:6379> hsetnx user:1002 name jack
(integer) 0
127.0.0.1:6379> hsetnx user:1002 age 10
(integer) 1
数据结构
Hash类型对应的数据结构是两种:ziplist(压缩列表),hashtable(哈希表),当field-value的长度较短且个数较少时,使用ziplist,否则使用hashtable
简介
Redis有序集合zset与普通集合set非常相似,是一个没有重复元素的字符串集合
不同之处是有序集合的每个成员都关联了一个评分(score),这个评分被用来从最低分到最高的方式排序集合中的成员。集合的成员是唯一的,但是评分可以是重复的。
因为元素是有序的,所以你也可以很快的根据评分或者次序(position)来获取一个范围的元素
访问有序集合的中间元素也是非常快的,因此你能够使用有序集合作为一个没有重复成员的智能列表
常用命令
zadd …
将一个或多个member元素及其score值加入到有序集合key当中
127.0.0.1:6379> zadd lang 200 java 300 cpp 400 python
(integer) 3
zrange [withscores]
返回有序集key中,下标在 < stop > 之间的元素,带withscopes,可以让分数一起和值返回到结果集合
127.0.0.1:6379> zrange lang 0 -1
1) "java"
2) "cpp"
3) "python"
zrangebyscope key minmax [withscores] [limit offset count]
返回有序集key中,所有score值介于min和max之间(包括min和max)的成员。
有序集成员按scope值递增(从小到大)次序排序
127.0.0.1:6379> zrange lang 0 -1 withscores
1) "java"
2) "200"
3) "cpp"
4) "300"
5) "python"
6) "400"
zrevrangescore key maxmin [withscopes] [limit offset count]
同上,改为从小到大排列
127.0.0.1:6379> zrangebyscore lang 200 400
1) "java"
2) "cpp"
3) "python"
zincrby 为元素的score加上增量
zrem 删除该集合下指定的元素
zcount < min> 统计该集合,分数区间的元素个数
zrank 返回该值在集合中的排名,从0开始
数据结构
SortedSet是Redis提供的一个非常特别的数据结构,一方面它等价于Java的数据结构Map,可以给每一个元素value赋予一个权重score,另一方面它又类似于TreeSet,内部的元素会按照权重scope进行排序,可以得到每个元素的名次,还可以通过scope的范围来获取元素的列表
zset底层使用了两个数据结构:
1)hash,hash的作用就是关联元素value和权重score,保障元素value的唯一性,可以通过元素value找到对应的scope值
2)跳跃表,跳跃表的目的在于给元素value排序,根据scope的范围获取元素列表
略
Redis发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送信息,订阅者(sub)接受信息
Redis客户端可以订阅任意数量的频道
打开一个客户端订阅channell
redis> subscribe channell
打开另一个客户端,给channell发布消息hello
publish channell hello
打开第一个客户端可以看到发送的消息
现代计算机用二进制作为信息的基本单位,1个字节等于8位,例如“abc”字符串是由三个字节组成,但实际计算机存储时将其用二进制来表示。
合理使用操作位能够有效的提高内存使用率和开发效率
Redis提供了Bitmaps这个“数据类型”可以实现对位的操作
1)Bitmaps本事不是一种数据类型,实际上它就是字符串(key-value),但是它可以对字符串的位进行操作
2)Bitmaps单独提供了一套命令,所以在Redis中使用Bitmaps和使用字符串的方法不太相同。可以把Bitmaps想 象成一个以位为单位的数组,数组的每个单元只能存储0和1,数组下标在Bitmaps中叫偏移量
常用命令
setbit
格式:setbit 设置Bitmaps中某个偏移量的值0或者1
127.0.0.1:6379> setbit lang 1 1 2 1 3 0
(error) ERR wrong number of arguments for 'setbit' command
127.0.0.1:6379> setbit lang 1 1
(integer) 0
127.0.0.1:6379> setbit lang 2 1
(integer) 0
注意:在第一次初始化Bitmaps时,加入偏移量非常大,那么整个初始化过程执行会比较慢,可能会造成Redis的阻塞
getbit
格式: 获取Bitmaps中某个偏移量的值
获取键的第offset位的值(从0开始计算)
127.0.0.1:6379> getbit lang 2
(integer) 1
bitcount
统计字符串被设置为1的bit数。一般情况下,给定的整个字符串都会被进行计数,通过指定额外的start或者end参数,可以让计数只在特定的位上进行。start和end参数的设置,都可以使用负数值:比如-1表示最后一个位,而-2表示倒数第二位,start和end表示bit组的字节的下标的数,二者皆包含。
bitop
简介
在工作当中,我们经常遇到与统计相关的功能需求,比如统计网站PV(pageview页面访问量),可以使用Redis的incr、incrby轻松实现。
但像UV(UniqueVisiter,独立访客)、独立IP数、搜索记录数等需要去重和计数的问题如何解决?这种求集合中不重复元素的个数问题称为基数问题
常见的解决方案:
1)数据存储在MySQL表中,使用distince count计算不重复的个数
2)使用Redis提供的hash、set、bitmaps等数据结构来处理
以上方案结果精确,但随着数据不断增加,导致占用空间越来越大,对于非常大的数据集是不切实际的
能否能够降低一定的精度来平衡存储空间?Redis推出了HyperLogLog
Redis HyperLogLog是用来做基数统计的算法,HyperLogLog的优点是,在输入元素的数量或者体积非常大非常大时,计算基数所需要的空间总是固定的、并且是很小的。
在Redis里面,每个HyperLogLog键只需要花费12KB内存,就可以计算接近2^64个不同的素数的基数。这和计算基数时,元素越多越耗费内存就越多的集合形成了鲜明对比。
但是,因为HyperLogLog只会根据输入元素来计算基数,而不会存输入元素本身,所以HyperLogLog不能像集合那样,返回输入的各个元素。
什么是基数?
比如数据集合{1,3,5,7,8},那么这个数据集的基数集为{1,3,5,7,8}基数为5。基数估计就是在误差可接受的范围内,快速计算基数。
常用命令
pfadd
pfcount
pfmerge
<dependency>
<groupId>redis.clientsgroupId>
<artifactId>jedisartifactId>
<version>3.2.0version>
dependency>
禁用Linux的防火墙:systemctl stop/disable firewalld.service
简单的测试
import org.junit.Test;
import redis.clients.jedis.Jedis;
public class AppTest
{
@Test
public void test01() {
Jedis jedis = new Jedis("192.168.200.128", 6379);
String ping = jedis.ping();
System.out.println(ping);
//打印输出PONG
}
}
String类型的测试
/*测试常用数据类型string*/
@Test
public void test01() {
jedis.set("username", "jack");
jedis.set("password", "hello");
List<String> list = jedis.mget("username", "password");
list.forEach(System.out::println);
Long username = jedis.strlen("username");
System.out.println(username);
jedis.append("username", " love rose!");
String username1 = jedis.get("username");
System.out.println(username1);
}
在pom.xml文件中引入redis相关的依赖
<denpendency>
<groupId>org.springframeword.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
denpendency>
<dependency>
<groupId>org.apache.commonsgroupId>
<artifactId>commons-pool2artifactId>
<version>2.6.0version>
dependency>
application.preperties中对redis的一些配置
创建Redis的配置类
测试
Redis事务是一个单独的隔离操作。事务的所有命令都会反序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送过来的额命令请求打断。
Redis事务的主要作用就是串联多个命令防止别的命令插队
从输入Multi命令开始,输入的命令都会依次进入命令队列中,但不会执行,知道输入Exec后,Redis会将之前的命令队列中的命令依次执行。
组队的过程可以通过discard来放弃组队。
代码示例:
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)> set key3 value3
QUEUED
127.0.0.1:6379(TX)> exec
1) OK
2) OK
3) OK
127.0.0.1:6379> keys *
1) "key2"
2) "password"
3) "username"
4) "key1"
5) "key3"
如果组队时发生错误,则该队列中的所有指令都不会成功
如果在执行过程中发生了错误,则在该错误语句执行失败,其余执行成功的指令仍然成功。
代码示例:
#组队过程出现错误,则该队列中的所有指令都不会成功
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)> set key3
(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> 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) (error) ERR value is not an integer or out of range
3) OK
127.0.0.1:6379> mget key1 key2
1) "value1"
2) "value2"
悲观锁
悲观锁,顾名思义就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次去拿数据的时候都认为别人会修改,所以每次在那数据的时候都会上锁,这样别人想拿这个数据就会锁定它直到拿到锁。传统的关系型数据库里面就用到了这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前就先上了锁。
乐观锁
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bmEYgNiJ-1683813026085)(C:\Users\12086\AppData\Roaming\Typora\typora-user-images\image-20221101161752743.png)]
乐观锁(Optimistic Lock),顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样就可以提高吞吐量。Redis就是利用这种check-and-set机制实现事务的。
WATCH key [key…]
在执行multi之前,先执行watch key1 [key2],可以监视一个(或者多个)key,如果事务执行之前这个(或这些)key被其他命令所改动,那么事务将会打断。
unwatch
取消watch命令对所有key的监视
如果在执行watch命令之后exec命令或者discard命令先被执行了的话,那么就不需要再执行unwatch了。
Redis中不能显示调用悲观锁,只能使用乐观锁
Lua脚本
Lua是一个小巧的脚本语言,Lua脚本可以很容易的被C/C++代码调用,也可以反过来调用C/C++的函数,Lua并没有提供强大的库,一个完整的Lua解释器也不过200k,所以Lua不适合作为开发独立应用程序的语言,而是作为嵌入式脚本语言。
很多应用程序、游戏使用Lua作为自己的嵌入式脚本语言,以此来实现可配置性、可扩展性。
Lua脚本在Redis中的优势
将复杂的或者多步的redis操作,写为一个脚本,一次提交给redis执行,减少反复连接redis的次数,提升性能。
Lua脚本是类似redis事务,有一定的原子性,不会被其他命令插队,可以完成一些redis事务性的操作。
但是注意redis的lua脚本功能,只有在redis2.6版本以上才可以使用
利用lua脚本淘汰用户,解决超卖问题。
redis2.6版本以后,通过lua脚本解决争抢问题
,实际上就是redis利用其单线程的特性,用任务队列的方式解决多任务并发问题。
在指定的时间间隔将内存中的数据集快照写入磁盘,也就是行话讲的Snapshot快照,它恢复时是将快照文件直接读到内存中。
Redis会单独创建(fork)一个子进程来进行持久化,会先
将数据写
入到一个临时文件
中,待持久化过程都结束了,再用这个临时文件替换上次持久化
好的文件。整个进程中,主进程是不进行任何IO操作的,这就保证了极高的性能,如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。RDB的缺点是最后一次持久化后的数据可能丢失
。
一样的进程
。新进程的所有数据(变量、环境变量、程序计数器等)数值都和原进程一致,但是是一个全新的进程,并作为原进程的子进程
“写时复制技术”
一般情况父进程和子进程会共用一段物理内存
,只有进程空间的各段的内容要发生变化时,才会将父进程的内容复制一份给子进程。先通过config get dir查询rdb文件的目录
将*.rdb的文件拷贝到别的地方
rdb的恢复
AOF(Append Only File)
以日志的形式来记录每个写操作(增量保存)
,将Redis执行过的所有写指令记录下来(读操作不记录
),只许追加文件但不可以改写文件
,Redis启动之初会读取该文件重新构建数据,换言之,redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。
1.AOF默认不开启
可以在redis.conf中配置文件的名称,默认为appendonly.aof
AOF文件的保存路径,同RDB的路径一致
2.AOF和RDB同时开启,redis听谁的?
AOF和RDB同时开启,系统默认读取AOF的数据(数据不会存在丢失)
AOF的备份机制和性能虽然和RDB不同,但是备份和恢复的操作同RDB一样,都是拷贝备份文件,需要恢复时再拷贝到Redis工作目录下,启动系统即加载配置文件
AOF的启动
AOF的修复
异常恢复(修改AOF文件)
始终同步,每次redis的写入都会立刻计入日志;性能较差但数据完整性比较好
appendsync everysec
每秒同步,每秒计入日志一次,如果宕机,本秒的数据可能丢失
appendsync no
redis不主动进行同步,把同步时机交给操作系统
优势:
劣势:
官方推荐两个都启用
如果对数据不敏感,可以选择单独使用RDB
不建议单独使用AOF,因为可能会出现BUG
如果只是做纯内存缓存,可以都不用
- RDB持久化方式能够在指定的时间间隔能对你的数据进行快照存储
- AOF持久化方式每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF命令以redis协议追加保存每次写的操作到文件末尾
- Redis还可以对AOF文件进行后台重写,使得AOF文件体积不至于过大
- 只做缓存:如果你只希望你的数据再服务器运行的时候存在,你也可以不使用任何持久化方式
- 同时开启两种持久化方式,当redis重启的时候会优先加载AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整
主机数据更新后根据配置和策略,自动同步到备机的master/slaver机制,Master以写为主,Slave以读为主
作用:读写分离,性能扩展。容灾快速恢复
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vnQaS5bE-1683813026085)(C:\Users\12086\AppData\Roaming\Typora\typora-user-images\image-20221101214403384.png)]
从服务器
挂掉之后,主服务器插入数据。重新启动从服务器,会发现从服务器的主从关系消失了,从服务器成为另一个主服务器,若要恢复,需要重新执行slave of操作主服务器
挂掉之后,重新启动从服务器,会发现从服务器的主从关系没有发生变化,仍然作为主服务器存在Slave
启动成功连接到master后会发送一个sync命令Master
接到命令启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令,在后台进程执行完毕之后,master将传送整个数据文件到slave,以完成一次完全同步slave
服务在接收到数据库文件数据后,将其存盘并加载到内存中Master
继续将新的所有收集到的修改命令依次传送给slave,完成同步master
,一次完全同步(全量复制)将被自动执行一主二仆
上同一主两从
薪火相传
主服务器 - > 从服务器 - > 从服务器
从服务器后面还作为其他从服务器的主服务器
反客为主
当一个master宕机后,后面的slave可以立刻升为master,其后面的slave不用做任何修改
当主服务挂掉之后,使用slave no one可以将slave升为master(手动模式)
反客为主的自动版,能够后台监控主机是否故障,如果故障了根据投票数自动将库转换为主库
步骤:
当主机挂掉,从机选举中产生新的主机
(大概10秒左右可以看到哨兵窗口日志,切换了新的主机)
哪个从机会被选举为主机呢?根据优先级别:slave-priority
原主机重启之后会变为从机
复制延迟
由于所有的写操作都是先在Master上操作,然后同步更新到Slave上,所以从Master同步到Slave机器有一定的延迟,当系统很繁忙的时候,延迟问题会更加严重,Slave机器数量的增加也会使得这个问题更加严重
选择主机的策略
优先级在redis.conf中默认:replica-priority 100,值越小优先级越高
偏移量是指获得原主机数据最全的
每个redis实例启动后都会随机生成一个40位的runid
Redis集群实现了对Redis的水平扩容,即启动N个redis节点,将整个数据库分布存储在这N个节点中,每个节点存储总数据的 1 / N。
Redis集群通过分区(partition)来提供一定程序的可用性:即使集群中有一部分节点失效或者无法进行通讯,集群也可以继续处理命令请求。
容量不够,redis如何进行扩容?
并发写操作,redis如何分摊?
另外,主从模式,薪火相传模式,主机宕机,导致ip地址发生变化,应用程序中配置需要修改对应的主机地址、端口号等信息
之前通过代理主机来解决,但是redis3.0
中提供了解决方案。就是无中心集群
配置。
修改配置文件
cluster-enabled yes 打开集群模式
cluster-config-file nodes-6379.conf 设定节点配置文件名
cluster-node-timeout 15000 设定节点失联时间,超过该时间(毫秒),集群自动进行主从切换
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T1iTsMu6-1683813026085)(C:\Users\12086\AppData\Roaming\Typora\typora-user-images\image-20221102083623847.png)]
下面还少了2条上面提到的配置
访问集群
redis-cli -c -p 端口号
查看集群信息
cluster nodes
Redis cluster如何分配这六个节点?
一个集群至少有三个主节点
选项 --cluster-replicas 1表示我们希望为集群中非每个主节点创建一个从节点
分配原则尽量保证每个主数据库运行在不同的IP地址,每个从库和主库不在同一个IP地址上
什么是slots?
(1)一个Redis集群包含16384个插槽(hash slot),数据库中的每个键都属于这16384个插槽中的其中一个。
(2)集群使用公式crc16(key) % 16384来计算键key属于哪一个插槽,其中crc16(key) 语句用于计算键key的crc16校验和。
(3)集群中的每个节点负责处理一部分插槽。举个例子,如果一个集群可以有主节点,其中:
节点A负责处理0号至5460号插槽
节点A负责处理5461号至10922号插槽
多键操作时不被支持的。
多键的Redis事务是不被支持的。Lua脚本不被支持。
由于集群方案出现较晚,很多公司都已经采用了其他的集群方案,而代理或者客户端分片的方案想要迁移到redis cluster,需要整体迁移而不是逐步过渡,复杂度较大。
问题描述
key对应的数据在数据源(数据库),每次针对此key的请求从缓存中获取不到,请求都会压到数据源,从而可能压垮数据源。比如用一个不存在的用户id获取用户信息,不论缓存还是数据库都没有,若黑客利用此漏洞进行攻击可能压垮数据库。
解决方案
一个一定不存在缓存以及查询不到的数据,由于缓存是不命中时被动写入的,并且处于容错考虑,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到存储层区查询,失去了缓存的意义。
(1)对空值进行缓存:如果一个查询返回的数据为空(不管数据是否存在),我们仍然把这个空结果(null)进行缓存,设置空结果的过期时间会很短,最多不超过5分钟。
(2)设置可访问的名单:使用bitmaps类型定义一个可以访问的名单,名单id作为bitmaps的偏移量,每次访问和bitmap里面的id进行比较,如果访问id不在bitmaps里面,进行拦截,不允许访问。
(3)采用布隆过滤器:布隆过滤器是1970年由布隆提出的,它实际上就是一个很长的二进制向量和一系列随机映射函数(哈希函数),原理类似于bitmaps。
(4)进行实时监控:当发现Redis的命中率开始急速降低,需要排查访问对象和访问的数据,和运维人员配合,可以设置黑名单限制服务。
问题描述
key对应的数据存在,但在redis中过期,此时若有大量的并发请求过来,这些请求发现缓存过期一般都会从后端数据库加载数据并设为到缓存,这个时大并发的请求可能会瞬间把后端数据库压垮。
解决方案
key可能会在某些时间点被超并发地访问,是一种非常“热点”的数据。这个时候,需要考虑一个问题:缓存被“击穿”的问题。
(1)预先设置热门数据:在redis高峰访问之前,把一些热门数据提前存入到redis里面,加大这些热门数据key的时长。
(2)实时调整:现场监控哪些数据热门,实时调整key的过期时长
(3):使用锁
问题描述
key对应的数据存在,但在redis中过期,此时若有大量的并发请求,这些请求发现缓存过期一般都会从后端数据库加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端数据库压垮。
缓存雪崩与缓存击穿的区别在于这里针对很多key缓存,前者是某一个key。
解决方案
缓存失效时的雪崩效应对底层系统的冲击非常可拍!
(1):构建多级缓存架构:Nginx缓存 + redis缓存 + 其他缓存(ehcache等)
(2):使用锁或者队列:用加锁或者队列的方式来保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上,不适用高并发情况
(3):设置过期标志更新缓存:记录缓存数据是否过期(设置提前量),如果过期会触发通知另外的线程区后台区更新实际key的缓存
(4):将缓存失效时间分散开:比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件
随着业务发展的需要,原单体机部署的系统被演化为分布式集群系统后,由于分布式系统多线程、多进程并且分布在不同及其上,这将使得原单机部署情况下的并发控制锁策略失效,单纯的Java API并不能提供分布式锁的能力。为了解决这个问题就需要一种跨JVM的互斥机制来控制共享资源的访问,这就是分布式锁要解决的问题!
分布式锁主流的实现方案:
每一种分布式锁解决方案都有各自的优缺点:
这里,我们基于redis实现分布式锁
使用setnx上锁,通过del释放锁
锁一直没有释放,设置key过期时间,自动释放
setnx users 10
expire users 10
上锁之后突然出现异常,无法设置时间了
#上锁时候同时设置时间就可以了
set users 10 nx ex 12
通过UUID防止锁误删的情况
需要通过Lua脚本保证原子性
为了保证分布式锁可用,我们至少要确保锁的实现同时满足一下四个条件:
Redis ACL是Access Control List (访问控制列表)的缩写,该功能允许根据可以执行的命令和可以访问的键来限制某些连接
在Redis5 版本之前,Redis安全规则只有密码控制还有通过rename来调整高危命令比如flushdb、keys* 等。Redis6则提供ACL的功能对用户进行更细粒度的权限控制。
参考网站:https://redis.io/topics/acl
简介
Redis6 终于支撑多线程了,告别单线程了吗?
IO多线程其实指客户端交互部分的网络I0交互处理模块多线程,而非执行命令多线程。Redis6执行命令依然是单线程。
原理架构
Redis 6加入多线程,但跟 Memcached 这种从I0 处理到数据访问多线程的实现模式有些差异。Redis的多线程部分只是用来处理网络数据的读写和协议解析,执行命令仍然是单线程。之所以这么设计是不想因为多线程而变得复杂,需要去控制 key、lua、事务,LPUSH/LPOP等等的并发问题。整体的设计大体如下:
另外,多线程IO默认是不开启的,需要在配置文件中配置
io-threads-do-reads yes
io-threads 4
之前老版 Redis想要搭集群需要单独安装ruby环境,Redis 5将redis-trib.rb的功能集成到redis-cli。另外官方redis-benchmark工具开始支持cluster模式了,通过多线程的方式对多个分片进行压测。
分布式锁解决方案都有各自的优缺点:
这里,我们基于redis实现分布式锁
使用setnx上锁,通过del释放锁
锁一直没有释放,设置key过期时间,自动释放
setnx users 10
expire users 10
上锁之后突然出现异常,无法设置时间了
#上锁时候同时设置时间就可以了
set users 10 nx ex 12
通过UUID防止锁误删的情况
需要通过Lua脚本保证原子性
为了保证分布式锁可用,我们至少要确保锁的实现同时满足一下四个条件:
Redis ACL是Access Control List (访问控制列表)的缩写,该功能允许根据可以执行的命令和可以访问的键来限制某些连接
在Redis5 版本之前,Redis安全规则只有密码控制还有通过rename来调整高危命令比如flushdb、keys* 等。Redis6则提供ACL的功能对用户进行更细粒度的权限控制。
参考网站:https://redis.io/topics/acl
简介
Redis6 终于支撑多线程了,告别单线程了吗?
IO多线程其实指客户端交互部分的网络I0交互处理模块多线程,而非执行命令多线程。Redis6执行命令依然是单线程。
原理架构
Redis 6加入多线程,但跟 Memcached 这种从I0 处理到数据访问多线程的实现模式有些差异。Redis的多线程部分只是用来处理网络数据的读写和协议解析,执行命令仍然是单线程。之所以这么设计是不想因为多线程而变得复杂,需要去控制 key、lua、事务,LPUSH/LPOP等等的并发问题。整体的设计大体如下:
另外,多线程IO默认是不开启的,需要在配置文件中配置
io-threads-do-reads yes
io-threads 4
之前老版 Redis想要搭集群需要单独安装ruby环境,Redis 5将redis-trib.rb的功能集成到redis-cli。另外官方redis-benchmark工具开始支持cluster模式了,通过多线程的方式对多个分片进行压测。