Redis学习笔记

文章目录

一、Redis基础

1.1、安装Redis

1.1.1 Windows安装Redis

下载zip包:https://github.com/MicrosoftArchive/redis/releases

zip解压后主要文件说明:

redis-server.exe----Reds服务
redis-cli.exe------Redis客户端
redis.windows.conf-----Redis配置

修改配置:(修改redis.windows.conf)

#允许其他ip访问本机:
bind 0.0.0.0
#设置密码:(默认没有启用密码)
requirepass 123456

启动:

启动服务:
	打开cmd,切换到zip解压后的目录
	执行:redis-server.exe  redis.windows.conf
	(Windows PowerShell中使用: .\redis-server.exe  redis.windows.conf )
	关闭命令行窗口,服务即关闭!!!
启动客户端:
	执行:redis-cli -h 127.0.0.1 -p 6379
	用户登录:127.0.0.1:6379> auth [password]

注册Redis服务:

方式一:使用cmd,切换到Redis目录:
执行:redis-server.exe --service-install redis.windows.conf --loglevel notice --service-name Redis

方式二:使用Windows PowerShell,切换到Redis目录:
执行: .\redis-server.exe  --service-install redis.windows.conf --loglevel notice --service-name Redis

启动服务:
	打开任务管理器,切换到服务,找到Redis服务,启动即可。

通过注册服务后,以后就可以开机启动了。

可视化界面工具:

redis-desktop-manager

IntelliJ IDEA 安装 Redis插件后,也可以直接连接Redis
1.1.2 Linux安装

linux系统安装Redis需要依赖gcc

	查看gcc是否安装(查看gcc版本):gcc --version
	如果未安装,搜索gcc安装包:yum search gcc
	是用包名下载:yum install gcc-c++

下载:

	官方下载地址:http://download.redis.io/releases/
	下载Redis6:
		cd /root
		wget -p /root/Download http://download.redis.io/releases/redis-5.0.5.tar.gz
	    wget -p /root/Download http://download.redis.io/releases/redis-6.2.7.tar.gz
	切换到压缩包目录下:cd /root//root/Downloads/download.redis.io/releases
	解压:tar -zxvf redis-5.0.5.tar.gz -C /usr/local
	编译(在/usr/local/redis-5.0.5下):make
	安装:
		进入到src下:cd src
		安装(默认安装到/usr/local/bin目录下):make install
		
	拷贝编译生成的相关可执行文件:为了以后快速启动
		创建文件夹:  
			存放redis的执行文件:mkdir -p /usr/local/redis/bin
	        存放redis的配置文件:mkdir -p /usr/local/redis/etc
					
		拷贝文件:(进入redis解压目录中)
				cp redis.conf  /usr/local/redis/etc
				cp sentinel.conf  /usr/local/redis/etc
				
				cp src/redis-server  /usr/local/redis/bin
				cp src/redis-benchmark  /usr/local/redis/bin
				cp src/redis-cli  /usr/local/redis/bin
				cp src/redis-check-aof  /usr/local/redis/bin
				cp src/redis-check-rdb  /usr/local/redis/bin
				cp src/mkreleasehdr.sh  /usr/local/redis/bin
				cp src/redis-sentinel  /usr/local/redis/bin 

修改配置:

修改配置信息:vim redis.conf
	后台启动:daemonize yes
	数据文件保存位置:dir /usr/local/redis/etc/
	允许跨网访问:bind 0.0.0.0
	
设置密码:(局域网内使用可以不使用密码)
	修改 redis.conf配置文件,设置:requirepass [密码]
	重启redis服务
	进入客户端:/usr/local/redis/bin/redis-cli -a [密码]
	进入客户端(方式二):/usr/local/redis/bin/redis-cli
		再输入:auth [密码]

手动启动Redis:

手动启动服务:(如过配置了redis.server文件可以直接使用:systemctl start redis.service )
	启动redis-server:/usr/local/redis/bin/redis-server /usr/local/redis/etc/redis.conf
	查看服务是否启动:ps -ef |grep redis
	关闭redis服务:pkill redis-server
	关闭redis服务:/usr/local/redis/bin/redis-cli shutdown
	
	启动客户端:./redis-cli
		测试:
			设置key-value:set [key] [value]
			通过key获取值:get [key]
			删除key-value:del [key]
	退出客户端:quit

linux自动启动Redis:

自动启动服务:(CentOS7)
	创建服务文件:
		进入系统服务文件目录下:cd /usr/lib/systemd/system
		创建服务文件:touch redis.service
		编辑文件:vim  /usr/lib/systemd/system/redis.service
			[Unit]
			Description=The redis-server Process Manager
			After=syslog.target network.target

			[Service]
			Type=forking
			PIDFile=/var/run/redis_6379.pid
			ExecStart=/usr/local/redis/bin/redis-server /usr/local/redis/etc/redis.conf
			ExecReload=/bin/kill -USR2 $MAINPID
			ExecStop=/bin/kill -SIGINT $MAINPID

			[Install]
			WantedBy=multi-user.target
		重新加载系统配置:systemctl daemon-reload
		本次启动服务:systemctl start redis.service 
		开机自动启动reis服务:systemctl enable redis.service 

1.2 Redis配置说明

#1、当客户端闲置多长时间后关闭连接,如果指定为 0,表示关闭该功能
timeout 300

#2、指定日志记录级别,Redis 总共支持四个级别:debug、verbose、notice、warning,默认为 verbose
loglevel notice

#3、日志记录方式,默认为标准输出,如果配置 Redis 为守护进程方式运行,而这里又配置为日志记录方式为标准输出,则日志将会发送给 /dev/null
logfile stdout

#4、设置数据库的数量,默认数据库为 0,可以使用 SELECT <dbid> 命令在连接上指定数据库 id
databases 16

#5、RDB数据同步间隔配置:指定在多长时间内,有多少次更新操作,就将数据同步到数据文件,可以多个条件配合
# RDB 文件是是经过压缩的二进制文件,占用空间很小,它保存了 Redis 某个时间点的数据集,很适合用于做备份。
# 如果想关闭RDB,则注释save  如果想删除之前的快照, 配置save '"。
# 如果同时开启了RDB和AOF下,会采用aof方式。AOF保存的数据方案时最完整的
save <seconds> <changes>
	#Redis 默认配置文件中提供了三个条件:
		#900秒内有1个更改时,进行同步
		save 900 1
		#300秒内有10个更改时,进行同步
		save 300 10
		#60秒内有10000个更改时,进行同步
		save 60 10000 
触发快照:1.save/bgsave命令 2.触发save point

#6、指定存储至本地数据库时是否压缩数据,默认为 yes,Redis 采用 LZF 压缩,如果为了节省 CPU 时间,可以关闭该选项,但会导致数据库文件变的巨大
rdbcompression yes

#7、指定本地数据库文件名,默认值为 dump.rdb
dbfilename dump.rdb

#8、指定本地数据库存放目录
dir ./

#9、设置当本机为 slave 服务时,设置 master 服务的 IP 地址及端口,在 Redis 启动时,它会自动从 master 进行数据同步
slaveof <masterip> <masterport>

#10、当 master 服务设置了密码保护时,slave 服务连接 master 的密码
masterauth <master-password>

#11、设置 Redis 连接密码,如果配置了连接密码,客户端在连接 Redis 时需要通过 AUTH <password>命令提供密码,默认关闭
requirepass foobared

#12、设置同一时间最大客户端连接数,默认无限制,Redis 可以同时打开的客户端连接数为 Redis 进程可以打开的最大文件描述符数,如果设置 maxclients 0,表示不作限制。当客户端连接数到达限制时,Redis 会关闭新的连接并向客户端返回 max number of clients reached 错误信息
maxclients 128

#13、指定 Redis 最大内存限制,Redis 在启动时会把数据加载到内存中,达到最大内存后,Redis 会先尝试清除已到期或即将到期的 Key,当此方法处理后,仍然到达最大内存设置,将无法再进行写入操作,但仍然可以进行读取操作。Redis 新的 vm 机制,会把 Key 存放内存,Value 会存放在 swap 区
maxmemory <bytes>

14、指定是否在每次更新操作后进行日志记录,Redis 在默认情况下是异步的把数据写入磁盘,如果不开启,可能会在断电时导致一段时间内的数据丢失。因为 redis 本身同步数据文件是按上面 save 条件来同步的,所以有的数据会在一段时间内只存在于内存中。默认为 no
appendonly no

#15、指定更新日志文件名,默认为 appendonly.aof
appendfilename appendonly.aof

#16、AOF指定更新日志条件,共有 3 个可选值:  
#1) no:表示等操作系统进行数据缓存同步到磁盘(快)  
#2) always:表示每次更新操作后手动调用 fsync() 将数据写到磁盘(慢,安全)        
#3) everysec:表示每秒同步一次(折衷,默认值)
appendfsync everysec


#17、指定是否启用虚拟内存机制,默认值为 no,简单的介绍一下,VM 机制将数据分页存放,由 Redis 将访问量较少的页即冷数据 swap 到磁盘上,访问多的页面由磁盘自动换出到内存中(在后面的文章我会仔细分析 Redis 的 VM 机制)
vm-enabled no

#18、虚拟内存文件路径,默认值为 /tmp/redis.swap,不可多个 Redis 实例共享
vm-swap-file /tmp/redis.swap

#19、将所有大于 vm-max-memory 的数据存入虚拟内存,无论 vm-max-memory 设置多小,所有索引数据都是内存存储的( Redis 的索引数据就是 keys ),也就是说,当 vm-max-memory 设置为 0 的时候,其实是所有 value 都存在于磁盘。默认值为 0
vm-max-memory 0

#20、Redis swap 文件分成了很多的 page,一个对象可以保存在多个 page 上面,但一个 page 上不能被多个对象共享,vm-page-size 是要根据存储的数据大小来设定的,作者建议如果存储很多小对象,page 大小最好设置为 32 或者 64 bytes;如果存储很大大对象,则可以使用更大的 page,如果不确定,就使用默认值
vm-page-size 32

#21、设置 swap 文件中的 page 数量,由于页表(一种表示页面空闲或使用的 bitmap)是在放在内存中的,在磁盘上每 8 个 pages 将消耗 1byte 的内存。
vm-pages 134217728

#22、设置访问 swap 文件的线程数,最好不要超过机器的核数,如果设置为 0,那么所有对 swap 文件的操作都是串行的,可能会造成比较长时间的延迟。默认值为 4
vm-max-threads 4

#23、设置在向客户端应答时,是否把较小的包合并为一个包发送,默认为开启
glueoutputbuf yes

#24、指定在超过一定的数量或者最大的元素超过某一临界值时,采用一种特殊的哈希算法
hash-max-zipmap-entries 64
hash-max-zipmap-value 512

#25、指定是否激活重置哈希,默认为开启(后面在介绍 Redis 的哈希算法时具体介绍)
activerehashing yes

#26、指定包含其它的配置文件,可以在同一主机上多个 Redis 实例之间使用同一份配置文件,而同时各个实例又拥有自己的特定配置文件
include /path/to/local.conf

1.3 Redis使用

1.3.1 基础命令

基础命令(必须掌握)

进入客户端:/usr/local/redis/bin/redis-cli

设置key-value:set [key] [value]

查看所有key:keys *

模糊查找key:keys [xxx]*

判断某个key时候存在:exists 【key】

查看key的类型:type [key]

删除key:del 【key】

设置过期时间:expire [key] [seconds]

查看剩余过期时间(-2表示已经过期,-1表示永不过期):ttl [key]

取消过期时间:persist [key]

切换数据库(默认16个库,从0到15):select [0,1,2~15]

基础命令(拓展)

重命名key:rename [oldkey] [newkey]

清空当前数据库:flushdb

清空所有数据库:flushall

判断是否存在某个key:exists [key]

选择数据库(0-15,默认db0):select [dbIndex]

数据迁移到其他库:move [key] [dbIndex]

随机返回某个key:randomkey

数据库key数量:dbsize

查看数据库信息:info

看配置信息:config get *

1.3.2 常用数据类型

基础数据类型:String、Hash、List、Set、Zset

1) String数据类型

基础数据类型,[value]最多可以存512M的数据

设置数据(覆盖已有):set [key] [value]
设置数据(不覆盖):setnx [key] [value]
设置缓存时间(秒):setex [key] [seconds] [value]
获取数据:get [key]
删除数据:del [key]
替换字符串:setrange [key] [start] [newstring]
返回旧值设置新值:getset [key] [newvalue]
追加字符串:append [key] [endstring]
获取字符串长度:strlen [key]
批量设置数据:mset [key1] [value1] [key2] [value2] [key3] [value3]
批量获取数据:mget [key] [key] [key ]

自增自减:value是int数字
	递增1:incr [key]
	递减1:decr [key]
	递增n:incrby [key] [n]
	递减n:decrby [key] [n]
2) Hash数据类型

Hash:更适合存储对象类型,比String占用更少的空间,更方便获取整个对象

设置数据:hset [hashname] [fieldkey1] [value1]
设置数据(不覆盖):hsetnx [hashname] [fieldkey1] [value1]
获取数据:hget [hashname] [fieldkey]
批量设置数据:hmset [hashname] [fieldkey1] [value1] [fieldkey2] [value2]
批量获取数据:hmget [hashname] [fieldkey1] [fieldkey2]
删除所有属性:hdel [hashname]
设置缓存时间:hsetex [hashname] 
返回hash长度:hlen [hashname]
返回hash所有key: hkeys [hashname]
返回hash所有value:hvals [hashname]
返回所有key-value:hgetall [hashname]
判断key是否存在:hexists [hashname] [field]
3) List数据类型

List:底层是一个双端链表结构,即可作为栈,也可作为队列

底层数据结构:快速链表

​ 1)列表元素比较少的时候,使用连续内存保存数据,是ziplist即压缩列表

​ 2)元素较多时候,将多个ziplist用双向指针组合成链表的方式存储数据,即quicklist

	从头部添加元素(栈):lpush [listkey] [value1] [value2]
	从尾部添加元素(队列):rpush [listkey] [value1] [value2]
	取出所有数据:lrange [listkey] 0 -1
	获取指点下标的元素:lindex [listkey] [index]
	获取原始个数:llen [listkey]
	
	插入元素:linsert [listkey] before [value1] [newvalue]
	根据下标替换元素:lset [list] [index] [newvalue]
	移除元素(移除n个值为value的元素):lrem [listkey] [num] [value]
	从头部移除一个元素:lpop [listkey] 
	从尾部移除一个元素:rpop [listkey] 
	保留范围数据(其他都删掉):ltrlm [listkey] [startIndex] [endIndex]
4) Set数据类型

特点:数据无序、元素不重复、底层是hash表(元素的添加删除查找复杂度都是O(1) )

	添加元素:sadd [setkey] [value1] [value2] [value3]
	查看所有元素:smembers [setkey]
	移除元素:srem [setkey] [value]
	查看元素个数:scard [setkey]
	判断集合是否包含某元素:sismember [setkey] [value]
	随机返回一个元素:srandmember [setkey]
	
	随机删除元素:spop [setkey] [count]
	差集(获取set1中不在set2中的元素):sdiff [set1] [set2]
		保存差集(获取set1中不在set2中的元素保存到set3中):sdiffstore [set3] [set1] [set2]
	获取交集:sinter [setkey1] [setkey2]
		保存交集:sinterstore [setkey1] [setkey2]
	获取并集:sunion [setkey1] [setkey2]
		保存并集:sunion [setkey3] [setkey1] [setkey2]
		
	移动元素:smove [set1] [set2] [set1value]
5) Zset数据类型

Zset:数据有序,元素不能重复;可以用来做排行

数据结构:

​ 1)使用hash,用于关联元素和score

​ 2)使用跳跃表:能快速给元素排序,找到范围内的元素

	添加元素(可覆盖):zadd [zsetkey] [core] [value]
	查看所有元素(索引):zrange [zsetkey] 0 -1 withscores
	根据序号查看指定范围数据:zrangebyscore [key] [core] [core]
	删除指定元素:zrem [zsetkey] [value]
	删除指定元素:zremrangebyscore [key] [core] [core]
	
	获取正序索引:zrank [zsetkey] [value]
	获取倒序索引:zrevrank [zsetkey] [value]
	元素个数:zcard [zsetkey]
	查询指定范围的元素个数:zcount [zsetkey] [core]	
1.3.3 Redis6 中新的数据类型
1) Bitmaps 类型

说明:

说明:Redis提供了Bitmaps这个“数据类型”可以实现对的操作:
1)Bitmaps本身不是一种数据类型, 实际上它就是字符串(key-value) , 但是它可以对字符串的位进行操作。
2)可以把Bitmaps想象成一个以位为单位的数组, 数组的每个单元只能存储0和1, 数组的下标在Bitmaps中叫做偏移量

使用场景:

例:统计系统每天访问的用户数量。用Bitmaps的偏移量作为用户id,数组每个单元值0表示未访问,1表示访问。

补充:档系统的用户访问量比较大的时候,这种数据类型来进行统计用户访问量是比较节省空间,如过系统访问量不多,用set保存数据也是比较节省空间的

命令:

setbit:设置Bitmaps中某个偏移量的值(0或1)

offset:偏移量从0开始(比如设置某用户某天是否访问过网站,将用户id设为偏移量,value置1表示访问过)

getbit:获取Bitmaps中某个偏移量的值,获取键的第offset位的值(从0开始算)

bitcount[start end] :统计字符串从start字节到end字节比特值为1的数量是字节不是位(统计字符串被设置为1的bit数。一般情况下,给定的整个字符串都会被进行计数,通过指定额外的 start 或 end 参数,可以让计数只在特定的位上进行。start 和 end 参数的设置,都可以使用负数值:比如 -1 表示最后一个位,而 -2 表示倒数第二个位,start、end 是指bit组的字节的下标数,二者皆包含。)

bitop and(or/not/xor) [key…]:bitop是一个复合操作, 它可以做多个Bitmaps的and(交集) 、 or(并集) 、 not(非) 、 xor(异或) 操作并将结果保存在destkey中。(月活用这个比较方便)
2) HyperLogLog 类型

**使用场景:**解决需要去重计数的问题场景(简单方便,且节省空间)

​ 在工作当中,我们经常会遇到与统计相关的功能需求,比如统计网站PV(PageView页面访问量),可以使用Redis的incr、incrby轻松实现。
但像UV(UniqueVisitor,独立访客)、独立IP数、搜索记录数等需要去重和计数的问题如何解决?这种求集合中不重复元素个数的问题称为基数问题。
解决基数问题有很多种方案:
​ 1)数据存储在MySQL表中,使用distinct count计算不重复个数
​ 2)使用Redis提供的hash、set、bitmaps等数据结构来处理
​ 以上的方案结果精确,但随着数据不断增加,导致占用空间越来越大,对于非常大的数据集是不切实际的。能否能够降低一定的精度来平衡存储空间?Redis推出了HyperLogLog

说明:

Redis HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定的、并且是很小的。(每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。)

因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,返回输入的各个元素。

命令:

pfadd < element> [element …]:添加指定元素到 HyperLogLog 中将所有元素添加到指定HyperLogLog数据结构中。如果执行命令后key估计的近似基数发生变化,则返回1,否则返回0。

pfcount [key …] 计算key的近似基数,可以计算多个key,比如用key存储每天的UV,计算一周的UV可以使用7天的UV合并计算即可

pfmerge [sourcekey …] 将一个或多个HLL合并后的结果存储在另一个HLL中,比如每月活跃用户可以使用每天的活跃用户来合并计算可得

3) Geospatial 类型

说明

中增加了对GEO类型的支持。GEO,Geographic,地理信息的缩写。该类型,就是元素的2维坐标,在地图上就是经纬度。redis基于该类型,提供了经纬度设置,查询,范围查询,距离查询,经纬度Hash等常见操作。

指令

geoadd< longitude> [longitude latitude member…] 添加地理位置(经度,纬度,名称)
	例:geoadd china:city 121.47 31.23 shanghai
	两极无法直接添加,一般会下载城市数据,直接通过 Java 程序一次性导入。有效的经度从 -180 度到 180 度。有效的纬度从 -85.05112878 度到 85.05112878 度。当坐标位置超出指定范围时,该命令将会返回一个错误。已经添加的数据,是无法再次往里面添加的。

geopos [member…] 获得指定地区的坐标值

geodist [m|km|ft|mi ] 获取两个位置之间的直线距离
    单位:
        m 表示单位为米[默认值]。
        km 表示单位为千米。
        mi 表示单位为英里。
        ft 表示单位为英尺。
    如果用户没有显式地指定单位参数, 那么 GEODIST 默认使用米作为单位

1.3.4 数据持久化

同时开启RDB和AOF时,数据恢复时Redis优先使用的是AOF的日志文件

1) RDB方式:

全称:Relational Database

说明:周期性的将内存中数据保存到硬盘中,默认dump.rdb文件(不适合实时系统使用)

配置说明:

Redis默认开启:
执行持久化:(默认配置如下)
	save 900 1			900秒内写了1个key
	save 900 10			300秒内写了10个key
	save 900 10000		60秒内写了10000个key

优点:

数据恢复快,适合大规模数据恢复

节省磁盘空间

缺点:

​ 1、因为fork线程会做数据同步(写时复制技术),故实际需要两倍的内存空间

​ 2、在最后一次数据同步时,可能会存在丢失数据问题

2) AOF方式:

全称:Append Only File

说明:将写数据的操作保存到日志当中,默认appendonly.aof(日常工作中常用)

配置说明:

执行持久:(以下3选1)
    appendfsync always		每次写操作进行(工作中)
    appendfsync everysec	每秒进行(默认)
    appendfsync no			不主动进行同步,把同步时机交给操作系统

异常恢复:

redis可以通过redis-check-aof --fix appendonly.aof 对损坏的aof文件进行恢复

重写压缩操作:

Redis4.0之后的版本,fork进程会将rdb快照,以二进制的形式附在新的aof文件头部,作为已有历史数据,替换掉历史的流水账日志记录。

配置说明:
	on-appendfsnc-on-rewrite  yes   不写入aof文件只写入缓存,重写时用户请求不阻塞,但期间宕机会数据丢失
	on-appendfsnc-on-rewrite  no	仍然会数据持久化,当进行重写时,会阻塞
	auto-aof-rewrite-percentage 100	设置重写基准版值:默认文件达到100%时才会重写(当前aof文件是上次重写后的文件的2倍时才是重写)
	auto-aof-rewrite-min-size 64mb	设置重写的基准值,默认64mb(当前aof文件达到64mb时开始重写)
	重写触发机制:
		当前aof文件大小 >= 上次aof文件大小 * (1+100%[默认])
		且
		当前aof文件大小 >= 64mb[默认]
		时,开始重写
		
		首次重写:2 * 64mb[默认] 
1.3.5 发布订阅

发布与订阅:
说明:Redis提供的发布与订阅是比较简单的,适用于即时通信(可主从同时使用)
订阅:subscribe [channel] [channe2]
退订:unsubscribe [channel] [channe2]
向指定频道发布:publish [channel] [message]

1.3.6 部署策略
1) 主从复制:

主从复制:(5.0之前称为maseter-slave;5.0开始称为maseter-replica

目的实现读写分离,Master主要负责写入,slave负责读取

特点:

​ 1、一个Master可以拥有多个slave;Master在同步数据时可以继续处理client请求,即不会阻塞

​ 2、从服务器宕机后,主服务器仍然有数据变更,从服务器启动后,仍然会读到变更后的数据

过程:slave与Master建立链接发送同步命令,Master后台将缓存数据保存到文件中,最后将文件发送给salve

注意:

​ redis设置主从后,从服务器不能进行写操作

配置主从1:(Redis5.0之前)

说明:只需修改“从”服务配置
修改redis.conf:vim /usr/local/redis/etc/reids.conf
配置修改配置项:slaveof [masterIp] [maseterPort]
	如果Master有密码,则需要修改配置项:masterauth [masterPassword]

配置主从2:(Redis5.0开始)

修改redis.conf:vim /usr/local/redis/etc/reids.conf
Master开放远程链接(注释掉其他bind):bind 0.0.0.0
配置修改配置项:replicaof [masterIp] [maseterPort]
	配置从库是否只读(默认yes):replica-read-only
	如果Master有密码,则需要修改配置项:masterauth [masterPassword]

指令:

查看主从信息(登录客户端发送):info replication
设置临时主机(登录客户端发送):slaveof [masterIp] [maseterPort]
2) 哨兵(切换主从):
  • 目的:实现redis高可用

  • 特点:进程独立,监控主从服务器状态,切换主从

  • 过程:

配置:(在主从复制的配置基础上)

编辑sentinel.conf:vim /usr/local/redis/etc/sentinel.conf
设置为后台启动:
禁止保护模式:protected-mode no
监听主服务器:sentinel monitor [masterName] [maseterIP] [maseterPort] 2 
	(注:masterNam表自定义服务器的名称;“2”代表只有两个或两个以上的哨兵认为主服务器不可用的时候,才会进行failover操作。)
定义服务密码(Master无密码时可不配置):sentinel auth-pass [masterName] [password]
启动哨兵进程:(所有主从Redis服务都启动后,再启动哨兵进程)
	启动:/usr/local/redis/bin/redis-sentinel /usr/local/redis/etc/sentinel.conf

缺点:

  • ​ 主从切换的时候,会几秒间隔时间,可能会丢失数据

故障恢复:

主挂了后,在从中选择主。选择过陈如下

1、选择优先级最高的:根据redis配置的优先级,在redis.conf中存在replice-priority 100[默认100,值越小优先级越高]
2、选择偏移量对大的:从中的数据与主最一致的(同步时间离当前最短的)、
3、选择runid最小的:redis启动会随机生成40位的id

原主服务器启动后,将做为从服务器

3) 集群:
  • 目的:实现redis高可用,扩容

  • 特点:

    1)机器更多,有多个主节点;

    2)主节点保存数据,类似数据库的分片机制;

    3)集群是一个整体,由redis自己处理数据存储到哪个节点,由redis自己处理(集群中每个主节分别占用有一个插槽范围,redis通过专门的算法计算key归属哪个插槽,进而确定数据存在哪个节点中);

    4)去中心化,登录任何一台redis,都可以操作

  • 环境要求:
    实际集群需要6台以上服务器
    需要安装ruby环境(redis6不在需要)

配置:

配置:(如过在同一台服务器模拟集群,则启动多个redis服务即可,注每个服务的端口要不同)
    设置端口(单机模拟,端口要不同):port 6379 
    绑定当前机器ip:bind [ip]
    指定数据文件存放位置:dir /usr/local/redis/etc
    开启集群:cluster-enabled yes
    指定集群配置文件: cluster-config-file nodes-6379.conf
    节点失效认定时间:cluster-node-timeout 5000
    打开AOF持久方式:appendonly yes

Redis6.0之前版本需要安装ruby环境:

yum install ruby
yum install rubygems
gem install redis 

故障恢复:

主挂从未挂:

从变成主,原主启动后变成从(与哨兵中的一致)

主从都挂了:

根据cluster-require-full-coverage配置确定是否让集群都不可用
cluster-require-full-coverage yes		表示整个集群都挂掉
cluster-require-full-coverage no		整个集群不挂,挂掉的主所在范围的插槽不可使用,无法存储数据

优缺点:

优点:

实现扩容
分摊压力
配置简单

缺点:

多建操作不支持
多建的事务也不支持,lua脚本也不支持
技术出来比较晚,公司的老项目做迁移的话,工程量比较大
4) 单机模拟Redis集群:

至少需求启动6个reids

1.创建文件夹:

同台机器启动多个redis,需要多个配置配置文件,没个配置文件中配置的端口不能相同

mkdir -p /usr/local/redis-cluster/bin
mkdir -p /usr/local/redis-cluster/7001
mkdir -p /usr/local/redis-cluster/7002
mkdir -p /usr/local/redis-cluster/7003
mkdir -p /usr/local/redis-cluster/7004
mkdir -p /usr/local/redis-cluster/7005
mkdir -p /usr/local/redis-cluster/7006

2.拷贝文件:

cp /usr/local/redis-5.0.5/redis.conf /usr/local/redis-cluster/7001
cp /usr/local/redis-5.0.5/src/redis-server  /usr/local/redis-cluster/bin
cp /usr/local/redis-5.0.5/src/redis-benchmark  /usr/local/redis-cluster/bin
cp /usr/local/redis-5.0.5/src/redis-cli  /usr/local/redis-cluster/bin
cp /usr/local/redis-5.0.5/src/redis-check-aof  /usr/local/redis-cluster/bin
cp /usr/local/redis-5.0.5/src/redis-check-rdb  /usr/local/redis-cluster/bin
cp /usr/local/redis-5.0.5/src/mkreleasehdr.sh  /usr/local/redis-cluster/bin
cp /usr/local/redis-5.0.5/src/redis-sentinel  /usr/local/redis-cluster/bin

配置文件也可以不拷贝,创建新的文件,文件头部使用 include [redis.conf文件路径] 引入公共配置,然后新配置文件中配置一些值

3.重命名文件:

mv /usr/local/redis-cluster/7001/redis.conf /usr/local/redis-cluster/7001/redis-7001.conf

4.修改配置:

vim /usr/local/redis-cluster/7001/redis-7001.conf
port 7001
bind 192.168.0.115
dir /usr/local/redis-cluster/7001/
cluster-config-file nodes-7001.conf
cluster-node-timeout 5000
appendonly yes

5.复制配置文件:

cp /usr/local/redis-cluster/7001/redis.conf /usr/local/redis-cluster/7002
cp /usr/local/redis-cluster/7001/redis.conf /usr/local/redis-cluster/7003
cp /usr/local/redis-cluster/7001/redis.conf /usr/local/redis-cluster/7004
cp /usr/local/redis-cluster/7001/redis.conf /usr/local/redis-cluster/7005
cp /usr/local/redis-cluster/7001/redis.conf /usr/local/redis-cluster/7006

7.修改配置文件名:

分别将7002、7003、7004、7005、7006下的redis-7001.conf,文件名以及配置中的7001改成相应的7002、70003等等

8.创建启动脚本:

  • 切换目录:cd /usr/local/redis-cluster
  • 创建脚本文件:touch start-redis-cluster.sh
  • 编辑脚本:vim touch start-redis-cluster.sh
v#!/bin/sh
REDIS_HOME=/usr/local/redis-cluster
$REDIS_HOME/bin/redis-server $REDIS_HOME/7001/redis-7001.conf
$REDIS_HOME/bin/redis-server $REDIS_HOME/7002/redis-7002.conf
$REDIS_HOME/bin/redis-server $REDIS_HOME/7003/redis-7003.conf
$REDIS_HOME/bin/redis-server $REDIS_HOME/7004/redis-7004.conf
$REDIS_HOME/bin/redis-server $REDIS_HOME/7005/redis-7005.conf
$REDIS_HOME/bin/redis-server $REDIS_HOME/7006/redis-7006.conf
  • 修改脚本文件为可执行文件:chmod 777 start-redis-cluster.sh
  • 执行启动脚本:/usr/local/redis-cluster/start-redis-cluster.sh

9.创建集群:

只需创建一次,如过需要删除集群,需删除所有/usr/local/redis-cluster/700*/nodes-700*.conf

/usr/local/redis-cluster/bin/redis-cli --cluster create 192.168.0.115:7001 192.168.0.115:7002 192.168.0.115:7003 192.168.0.115:7004 192.168.0.115:7005 192.168.0.115:7006 --cluster-replicas 1

Redis6需要在src下执行命令

/usr/local/redis-6.0.9/src/redis-cli --cluster create  --cluster-replicas 1 192.168.0.115:7001 192.168.0.115:7002 192.168.0.115:7003 192.168.0.115:7004 192.168.0.115:7005 192.168.0.115:7006

–cluster-replicas 1

cluster-replicas 1  表示每个主节点创建一个从节点

10.测试:

链接客户端(用任何一个节点连接都可以): /usr/local/redis-cluster/bin/redis-cli -c -h 192.168.0.115 -p 7001
查看集群所有节点:cluster nodes
设置单个值:set k1 v1
批量设置(需要对key进行分组):mset k1{[组名]} v1  k2{[组名]} v2
查看插槽某个key的所在的插槽值:cluster keyslot [key名]
查看某个插槽内有几个key(只能查看当前主机所在插槽的范围):cluster countkeysinslot [插槽值]
获取某个插槽中的所有key(只能查看当前主机所在插槽的范围):cluster getkeysinslot [插槽值]

11.Jedis操作集群

可以使用JedisCluster操作集群
还有其他方式,自行百度

二、Redis中事务和锁机制

2.1 Redis事务控制

2.1.1 Redis事务

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

Redis事务流程:开启事务–》指令队列–》执行事务/取消事务

2.1.2 事务命令:Multi、Exec、Discard

命令说明:

Multi 开启事务

Exec 执行事务

Discard 取消事务

测试事务执行:

127.0.0.1:0>multi
"OK"
127.0.0.1:0>set k1 v1
"QUEUED"
127.0.0.1:0>set k2 v2
"QUEUED"
127.0.0.1:0>exec
 1)  "OK"
 2)  "OK"

测试取消事务:

127.0.0.1:0>multi
"OK"
127.0.0.1:0>set k3 v3
"QUEUED"
127.0.0.1:0>set k4 v4
"QUEUED"
127.0.0.1:0>discard
"OK"
127.0.0.1:0>
2.1.3 事务错误处理
  • 命令组队时失败:

组队过程中,若指令失败,则在事务执行时,会自动取消整个事务

127.0.0.1:0>multi
"OK"
127.0.0.1:0>set k1 v1
"QUEUED"
127.0.0.1:0>set k2
"ERR wrong number of arguments for 'set' command"
127.0.0.1:0>set k3 v3
"QUEUED"
127.0.0.1:0>exec
"EXECABORT Transaction discarded because of previous errors."
127.0.0.1:0>
  • 命令执行时失败:

命令组队时,没有失败,执行时某条指令失败,此时会跳过执行失败的命令,继续执行队列中的下一条指令

如下测试:第二条指令对非数字的value进行加1操作

127.0.0.1:0>multi
"OK"
127.0.0.1:0>set k1 v1
"QUEUED"
127.0.0.1:0>incr k1
"QUEUED"
127.0.0.1:0>set k2 v2
"QUEUED"
127.0.0.1:0>exec
 1)  "OK"
 2)  "ERR value is not an integer or out of range"
 3)  "OK"
127.0.0.1:0>

2.2 Redis锁机制

2.2.1 悲观锁

每次取数据时,都认为别人会修改数据,故每次取数据时都对数据加锁,即操作公共资源之前进行加锁。(传统关系型数据库用的就是这种机制)

2.2.2 乐观锁

每次读取数据时,认为别人不会修改数据,故此时不加锁,但是需要修改数据需要判断别人有没有修改数据,可以使用版本号来进行判断。

适用:乐观锁适用于多读的应用,这样可以提高系统的吞吐量。(Redis中就是使用这种)

2.2.3 乐观锁带来的问题:

问题你描述:库存遗留问题

​ 在高并发情况下,如果两千个人同时获取到V1.0版本的数据,然后同时提交的话,那么最终将只会有一个人修改成功,其余的1999人都会修改失败。

解决方案:

Redis 2.6以上的版本,可以通过库存遗留问题可以通过。

LUA脚本就是将复杂的或者多步的redis操作,写成一个脚本,一次提交给Redis执行,减少反复连接redis的次数。提升性能。
LUA脚本是类似redis事务,有一定的原子性,不会被其他命令插队,可以完成一些redis事务性的操作。

**解决秒杀案例中的:**库存遗留问题Lua脚本示例

local userid=KEYS[1];
local prodid=KEYS[2];
local qtkey="sk:"..prodid..":qt";
local userskey="sk:"..prodid..":user";
local userExists=redis.call("sismember",userskey,userid);
if tonumber(userExists)==1 then
  return 2;
end
local num=redis.call("get",qtkey);
if tonumber(num)<=0 then
  return 0;
else
  redis.call("decr",qtkey);
  redis.call("sadd",userskey,userid);
  end
  return 1

**测试类:**使用jedis.scriptLoad()方法调用lua脚本

@Service
public class SeckillByScript {
    static String secKillScript1 = "local userid=KEYS[1];\n" +
            "local prodid=KEYS[2];\n" +
            "local qtkey=\"sk:\"..prodid..\":qt\";\n" +
            "local userskey=\"sk:\"..prodid..\":user\";\n" +
            "local userExists=redis.call(\"sismember\",userskey,userid);" +
            "if tonumber(userExists)==1 then\n" +
            "  return 2;\n" +
            "end\n" +
            "local num=redis.call(\"get\",qtkey);\n" +
            "if tonumber(num)<=0 then\n" +
            "  return 0;\n" +
            "else\n" +
            "  redis.call(\"decr\",qtkey);\n" +
            "  redis.call(\"sadd\",userskey,userid);\n" +
            "  end\n" +
            "  return 1";

    public boolean doSecKill(String userid, String prodid) {
        JedisPool jedisPool = JedisPoolUtil.getJedisPool();
        Jedis jedis = jedisPool.getResource();
        String sha1 = jedis.scriptLoad(secKillScript1);
        Object result = jedis.evalsha(sha1, 2, userid, prodid);
        String reString = String.valueOf(result);
        if ("0".equals(reString)) {
            System.out.println("已抢空!");
            return false;
        } else if ("1".equals(reString)) {
            System.out.println("抢购成功");
            return true;
        } else if ("2".equals(reString)) {
            System.out.println("该用户已抢过!");
            return false;
        } else {
            System.out.println("抢购异常");
            return false;
        }
    }
}

2.2.4 watch指令

指令:watch

在执行multi之前先执行watch [key1 key2 …] 监视一个或多个key。如果事务在执行之前,被监视的key的value被其他程序修了,则当前事务被打断。

测试:

  • 准备数据:
127.0.0.1:6379> set k1 1000
OK
  • 客户端1:使用watch监控,并创建事务准备修改数据,事务先不执行
127.0.0.1:6379> watch k1
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incr k1
QUEUED
  • 客户端2:使用watch监控,并进行数据修改(可以修改成功)
127.0.0.1:0>watch k1
"OK"
127.0.0.1:0>incr k1
"1001"
127.0.0.1:0>
  • 客户端1:执行事务(事务终止,队列中的所有指令都不执行)
127.0.0.1:6379> exec
(nil)
127.0.0.1:6379>

指令:unwatch

unwatch:取消watch对所有key的监视。

注:在执行watch最后,事务执行了exce或discard,那么不需要执行unwach

3、事务的特性

隔离性

事务中的指令都按顺序执行,执行过程中不会被其他客户端发来的指定打断

没有隔离级别

队列中的指令在事务提交前都不会被实际执行

不保障原子性

事务执行过程中有一条指令执行失败,后续指令仍然会继续执行,不会进行回滚

三、Spring整合Redis

3.1 概述

Jedis是Redis官方推荐的Java连接开发工具。要在Java开发中使用好Redis中间件,必须对Jedis熟悉才能写成漂亮的代码。

在 springboot 1.5.x版本的默认的Redis客户端是 Jedis实现的,springboot 2.x版本中默认客户端是用 lettuce实现的

Jedis与Lettuce

说明:

LettuceJedis 的定位都是Redis的client,所以他们当然可以直接连接redis server

​ Jedis在实现上是直接连接的redis server,如果在多线程环境下是非线程安全的,这个时候只有使用连接池,为每个Jedis实例增加物理连接

​ Lettuce的连接是基于Netty的,连接实例(StatefulRedisConnection)可以在多个线程间并发访问,应为StatefulRedisConnection是线程安全的,所以一个连接实例(StatefulRedisConnection)就可以满足多线程环境下的并发访问,当然这个也是可伸缩的设计,一个连接实例不够的情况也可以按需增加连接实例。

3.2 Springboot2.0通过Lettuce整合Redis

  • 说明

  • 依赖

因为 springboot2.0中默认是使用 Lettuce来集成Redis服务,spring-boot-starter-data-redis默认只引入了 Lettuce包,并没有引入 jedis包支持。所以在我们需要手动引入 jedis的包,并排除掉 lettuce的包,pom.xml配置如下:

        <!--springboot整合redis,springboot2.0默认使用Lettuce-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!-- redis需要使用连接池 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>
  • 配置

#redis主机地址
spring.redis.host=127.0.0.1
#redis连接端口
spring.redis.port=6379
#redis密码
spring.redis.password=123456
#redis数据库索引
spring.redis.database=0
#redis连接超时时间
spring.redis.timeout=3000s
#redis最大连接数
spring.redis.lettuce.pool.max-active=100
#最多维持空闲连接
spring.redis.lettuce.pool.max-idle=10
#最少维持空闲连接
spring.redis.lettuce.pool.min-idle=10
#连接池出借连接的最长期限
spring.redis.lettuce.pool.max-wait=30000s

  • 使用示例

配置类:

@Configuration
public class RedisConfig {
    /**
     * 自定义RedisTemplate
     * <p>
     * 默认情况下的模板只能支持 RedisTemplate<String,String>,只能存入字符串,
     * 很多时候需要自定义 RedisTemplate ,设置序列化器,这样我们可以很方便的操作实例对象
     *
     * @param connectionFactory
     * @return
     */
    @Bean
    public RedisTemplate<String, Serializable> redisTemplate(LettuceConnectionFactory connectionFactory) {
        RedisTemplate<String, Serializable> redisTemplate = new RedisTemplate<>();
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        redisTemplate.setConnectionFactory(connectionFactory);
        return redisTemplate;
    }
}

实体类:

@Data
public class User implements Serializable {
    private static final long serialVersionUID = -1622274450182629030L;
    private Integer id;
    private String username;
    private Integer age;

    public User() {
    }

    public User(Integer id, String username, Integer age) {
        this.id = id;
        this.username = username;
        this.age = age;
    }
}

测试:

@SpringBootTest
@Slf4j
class Redis01ApplicationTests {
    @Autowired
    private RedisTemplate<String, Serializable> redisTemplate;

    @Test
    public void testRedis() {
        String key = "user:1";
        redisTemplate.opsForValue().set(key, new User(1, "pjmike", 20));
        User user = (User) redisTemplate.opsForValue().get(key);
        log.info("uesr: " + user.toString());
    }

}

3.3 SpringBoot2.0通过Jedis整合Redis

  • 说明:

​ 因为 springboot2.0中默认是使用 Lettuce来集成Redis服务,spring-boot-starter-data-redis默认只引入了 Lettuce包,并没有引入 jedis包支持。所以在我们需要手动引入 jedis的包,并排除掉 lettuce的包

  • 依赖:

springboot和jedis版本需要对应才行。
springboot2.1.3.RELEASE 使用 jedis2.9.0
springboot2.2.0.RELEASE 使用 jedis3.1.0
springboot2.3.3.RELEASE 使用 jedis3.1.0

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <!--SpringBoot2.0使用Jedis,排除lettuce包-->
            <exclusions>
                <exclusion>
                    <groupId>io.lettuce</groupId>
                    <artifactId>lettuce-core</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <!--SpringBoot2.0使用Jedis,引入jedis-->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>3.1.0</version>
        </dependency>
        <!-- spring2.X集成redis所需common-pool2,使用jedis必须依赖它 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
            <version>2.6.2</version>
        </dependency>
  • 配置:

#redis连接端口
spring.redis.port=6379
#redis密码
spring.redis.password=123456
#redis数据库索引
spring.redis.database=0
#redis连接超时时间
spring.redis.timeout=3000s
#redis最大连接数
spring.redis.jedis.pool.max-active=100
#最多维持空闲连接
spring.redis.jedis.pool.max-idle=10
#最少维持空闲连接
spring.redis.jedis.pool.min-idle=10
#连接池出借连接的最长期限
spring.redis.jedis.pool.max-wait=30000s

  • 使用示例:

配置类型:

/**
 * Redis配置类
 *
 * @AutoConfigureAfter(RedisAutoConfiguration.class) 是让我们这个配置类在内置的配置类之后在配置,这样就保证我们的配置类生效,并且不会被覆盖配置
 */
@Configuration
@AutoConfigureAfter(RedisAutoConfiguration.class)
public class RedisConfig {
    /**
     * 配置自定义redisTemplate
     *
     * @param redisConnectionFactory
     * @return
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);
        //1:使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        template.setDefaultSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();
        template.setValueSerializer(jackson2JsonRedisSerializer);
        //2:使用StringRedisSerializer来序列化和反序列化redis的key值
        template.setKeySerializer(RedisSerializer.string());
//        template.setValueSerializer(RedisSerializer.string());
//        template.setHashKeySerializer(RedisSerializer.string());
//        template.setHashValueSerializer(RedisSerializer.string());

        // 配置共享本地连接设置
//        template.setShareNativeConnection(false);
        return template;
    }
}

实体类:

@Data
public class User implements Serializable {
    private static final long serialVersionUID = -1622274450182629030L;
    private Integer id;
    private String username;
    private Integer age;

    public User() {
    }

    public User(Integer id, String username, Integer age) {
        this.id = id;
        this.username = username;
        this.age = age;
    }
}

测试:

@SpringBootTest
@Slf4j
class Reids02ApplicationTests {
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Test
    public void testRedis() {
        String key = "user:2";
        redisTemplate.opsForValue().set(key, new User(1, "atest", 20));
        User user = (User) redisTemplate.opsForValue().get(key);
        log.info("uesr: " + user.toString());
    }

    @Test
    public void testRedisTemplate() {
        final String key = "obj";
        List<String> strs = Arrays.asList("d", "b", "a", "c", "a");
        ValueOperations<String, Object> opsForValue = redisTemplate.opsForValue();
        opsForValue.set(key, strs);
        Object str = opsForValue.get(key);
        System.out.println(str);
    }
}

3.3 基本使用(人工创建连接池)

依赖:

        <!--SpringBoot2.0使用Jedis,引入jedis-->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>3.1.0</version>
        </dependency>
        <!-- spring2.X集成redis所需common-pool2,使用jedis必须依赖它 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
            <version>2.6.2</version>
        </dependency>

配置类:

@Configuration
public class JedisPoolFactory {
    /**
     * 自动注入redis配置属性文件
     */
    @Autowired
    private RedisProperties properties;

    @Bean
    public JedisPool getJedisPool() {
        JedisPoolConfig config = new JedisPoolConfig();
        config.setMaxIdle(properties.getJedis().getPool().getMaxIdle());
        config.setMaxTotal(properties.getJedis().getPool().getMaxActive());
        config.setMaxWaitMillis(properties.getJedis().getPool().getMaxWait().toMillis());
        JedisPool pool = new JedisPool(config, properties.getHost(), properties.getPort(), 100, "123456");
        return pool;
    }
}

测试:

@SpringBootTest
@Slf4j
class Reids02ApplicationTests {
    @Resource
    private JedisPool jedisPool;

    @Test
    public void testGetJedis() {
        Jedis jedis = jedisPool.getResource();
        jedis.set("jedisKey", "testVlue");
        System.out.println(jedis.keys("*"));
    }
}

代码中使用:

Jedis调用方法,其实与Redis的命令息息相关。例如keys *** 命令可以用jedis.keys(“*”)**;

    @Test
    void contextLoads() {
        Jedis jedis = new Jedis("127.0.0.1", 6379);
        //设置密码
        jedis.auth("123456");
        //基本使用
        jedis.keys("*");
        jedis.set("k1", "v1");
        Set<String> k1 = jedis.keys("k1");
        System.out.println(k1);
        String k11 = jedis.get("k1");
        System.out.println(k11);
        //关闭连接
        jedis.close();
    }

四、Redis6新特性:

4.1 ACL

描述:

redis6之后,提供了更细粒度的权限控制
1、接入权限:用户名和密码
2、控制可以执行的命令
3、控制可以操作的key

指令示例:

#查看权限列表
127.0.0.1:6379> acl list
1) "user default on nopass ~* &* +@all"   
# default表示用户名,on表示是否启用,nopass表示没有密码,~*表示可操作性的key, +@all表示可执行的命令

#增加用户权限
127.0.0.1:6379> acl setuser lilei
OK
127.0.0.1:6379> acl list
1) "user default on nopass ~* &* +@all"
2) "user lilei off &* -@all"

#查看当前操作用户
127.0.0.1:6379> acl whoami
"default"

#设置一个密码是123456 只能够使用get的命令的用户,
#并且在key前一定要加上`cached:`这个限制
127.0.0.1:6379> acl setuser lilei1 on >123456 ~cached:* +get
OK
#检查是否设置成功
127.0.0.1:6379> acl list
1) "user default on nopass ~* &* +@all"
2) "user lilei off &* -@all"
3) "user lilei1 on #8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92 ~cached:* &* -@all +get"
#切换用户操作
127.0.0.1:6379> auth lilei1 123456
OK
#没有权限的key,无法操作
127.0.0.1:6379> get a
(error) NOPERM this user has no permissions to access one of the keys used as arguments
#有权限的key可以正常操作
127.0.0.1:6379> get cached:a
(nil)

4.2 IO多线程

描述:

6.0之后增加了多线程的实现,多线程使用在io的操作上,指令的执行还是只有一个单线程,多的io线程用于读read或者写write,
多线程不会同时执行读写操作。所有多出的线程要不是全部用于读,要不全部都是用于写。

#6.0多线程的配置默认情况下是关闭的,需要通过配置开启。开启多线程后,还需要设置线程数,否则是不生效的。同样修改redis.conf配置文件
io-threads 4 #默认开启4个线程,其中包含1个main线程和3个写线程,默认情况下不开启读线程
io-threads-do-reads no#默认情况下,开启了多线程之后是用来写操作的,
如果此设置为yes则表示新开启的线程用于读操作

官方建议:

4核的机器建议设置为2或3个线程,8核的建议设置为6个线程,线程数一定要小于机器核数。官方认为超过了8个基本就没什么意义了

Redis6.0之前的版本真的是单线程吗?

所谓的单线程:Redis在处理客户端的请求时,包括获取 (socket 读)、解析、执行、内容返回(socket写) 等都由一个顺序串行的主线程处理。

存在其他辅助线程:Redis4.0之后,除了主线程外,还有其他辅助线程在处理一些较为缓慢的操作,例如数据持久化、清理脏数据、无用连接的释放、大key的删除等等。

Redis6.0之前为什么一直不使用多线程?

1、Redis几乎不存在CPU成为瓶颈的情况,Redis主要受限于内存和网络。(普通的Linux系统上,Redis通过使用pipelining每秒可以处理100万个请求)

2、Redis通过AE事件模型以及IO多路复用等技术,处理性能非常高,因此没有必要使用多线程

3、单线程机制使得 Redis 内部实现的复杂度大大降低

Redis6.0为什么要引入多线程呢?

80%公司实际不需要,主要是针对QPS动不动就上亿项目,常需要搭建redis集群,集群需要数据同步,此时redis瓶颈就体现IO上,引入IO多线程能分摊IO负荷

五、Redis常见问题

5.1 经典问题

5.1.1 缓存穿透

现象:

应用服务压力增大,redis状态正常但是命中率降低,数据库中查询不到数据

分析:

redis查询不到数据,出现很多非常常访问,可能受到恶意攻击

解决方案:

  • 对空值做缓存:对查询不到的数据做缓存,但是缓存时间不以过长(临时处理方案)
  • 设置白名单:可以利用redis的bitmaps数据类型,设置可以访问的用户(缺点每次请求都需要判断)
  • 采用布隆过滤:原理与bitmaps类似,即判断数据存不存在
  • 实时监控:发现redis命中率急速降低时,对系统的访问用户进行排查
5.1.2 缓存击穿

现象:

数据库压力瞬间增大;redis中没有大量的key过期;redis正常运行

分析:

redis中某个key过期了,系统大量访问该key,在再次缓存改key之前,有大量请求直接查询数据库

解决案:

设置热门数据:将系统的热门数据进行缓存,并适当延长过期时间

实时监控:实时监控redis看哪些是热门数据,针对热门数据延长过期时间

使用双重检测锁:实现高并发情况下,只查询一次数据库,查询数据后立马缓存

5.1.3 缓存雪崩

现象:

数据压力增大,导致系统崩溃

分析:

某一时刻大片缓存数据因超时而被清理,此时大量查询数据库

解决方案:

**构建多级缓存:**采用不同层级多处缓存;(机构相对复杂)

**使用锁或者队列:**锁能降低数据库访问压力,队列有削峰的作用

**设置过期标识更新缓存:**设置标识,监控快过期时,进行数据刷新

**分散过期时间:**根据业务将相应的数据的缓存时间分开设置,不要集中在某一时刻

5.1.4 Key的过期策略:

Redis的Key有3种过期删除策略,具体如下:

  • 定时删除
    **原理:**在设置键的过期时间的同时,创建一个定时器(timer),让定时器在键的过期时间来临时,立即执行对键的删除操作
    **优点:**能够很及时的删除过期的Key,能够最大限度的节约内存
    **缺点:**对CPU时间不友好,如果过期的Key比较多时,可能会占用相当一部分CPU时间,对服务器的响应时间和吞吐量造成影响
  • 惰性删除
    原理:取出键时才对键进行过期检查,如果发现过期了就会被删除
    **优点:**对CPU友好,能够最大限度的节约CPU时间
    **缺点:**对内存不友好,过期的Key会占用内存,造成浪费
  • 定期删除
    **原理:**定期删除策略是定时删除策略和惰性删除策略的一个折中。定期删除策略每隔一段时间执行一次删除过期键的操作,并通过限制删除操作执行的时长和频率来减少删除操作对CPU时间的影响
    **优点:**对CPU时间和内存空间的一种权衡,可以根据实际使用情况来调整删除操作执行的时长和频率
    **缺点:**确定删除操作执行的时长和频率很难。如果删除操作执行的太频繁,或者执行的时间太长,退化成定时删除策略;如果删除操作执行的太少,或者执行时间太短,退化成惰性删除策略
  • **Redis服务器实际使用的是惰性删除和定期删除两种策略:**通过配合使用这两种删除策略,服务器可以很好地在合理使用CPU时间和避免浪费内存空间之间取得平衡。Redis默认每隔100ms随机抽取一些设置了过期时间的key,检查是否过期,如果过期就删除。
5.1.5 内存淘汰机制

如果Redis服务器打开了maxmemory选项,并且服务器占用的内存数超过了maxmemory选项所设置的上限值时,会进行内存淘汰,常见的淘汰策略如下:

volatile-lru:从已设置过期时间的数据集中挑选最近最少使用的数据淘汰
volatile-ttl:从已设置过期时间的数据集中挑选将要过期的数据淘汰
volatile-random:从已设置过期时间的数据集中任意选择数据淘汰
volatile-lfu:从已设置过期时间的数据集挑选使用频率最低的数据淘汰
allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
allkeys-lfu:从数据集(server.db[i].dict)中挑选使用频率最低的数据淘汰
allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
no-enviction(驱逐):禁止驱逐数据,这也是默认策略。意思是当内存不足以容纳新入数据时,新写入操作就会报错,请求可以继续进行,线上任务也不能持续进行,采用no-enviction策略可以保证数据不被丢失。
5.1.5 分布式锁

目标:

针对分布系统,所有机器都能公用的锁即分布式锁

常见实现方案:

基于数据库实现

基于缓存实现(Redis)

基于Zookeeper

Redis实现分布式锁优缺点:

优点:

实现原理简单,性能高

缺点:

不是100%可靠:如果在redis集群中使用分布式锁,主从复制存在延时,这样的延时不能保证锁的安全性。

1)redis实现分布式锁
  • 使用setnx

setnx key value 表示不覆盖设置key-value,即原有key已经存在,即设置值不生效(这本身就是锁的意思)

上锁:setnx [key] [value]

手动释放锁:del [key]

手动防止锁未释放(设置过期时间):expire [key] [second]

缺点:使用setnx设置后,如果没有及时设置过期时间,还是存在不释放锁的风险

  • 使用set

set [key] [value] nx ex [second] :使用set设置数据的同时,进行上锁,且设置过期时间

上锁:set [key] [value] nx ex [second]
释放锁:del [key]
2)UUID防止误删

描述:

在程序的末尾手动释放锁之前,发生了阻塞,且阻塞时间正好超过了key的过期时间,那么其他线程就会获得锁,此时被阻塞的线程手动释放锁时,可能释放是别人的。

解决方式:

使用UUID标识:对上锁的资源,的值用唯一的数据进行标识(例如UUID),程序手动释放锁时,对值做比较。

    @PostMapping("/testLock")
    public void testLock() {
        String yyds = UUID.randomUUID().toString();
        /*获取锁*/
        Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", yyds, 3, TimeUnit.SECONDS);
        /*获取锁成功*/
        if (lock) {
            Object num = redisTemplate.opsForValue().get("num");
            if (StringUtils.isEmpty(num)) {
                return;
            }
            int tic = Integer.parseInt(num + "");
            redisTemplate.opsForValue().set("num", ++tic);
            /*获取锁的uuid,只能释放自己的锁*/
            String uuidLock = (String)redisTemplate.opsForValue().get("lock");
            if(yyds.equals(uuidLock)){
                /* 释放锁,del*/
                redisTemplate.delete("lock");
            }
            
        } else {
            try {
                Thread.sleep(100);
                testLock();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
3)Lua脚本保证删除原子性

描述:

使用UUID做防止误删除时,假如Java判断UUID相等后程序发生了阻塞,key正好过期,其他线程获得锁,此时被阻塞线程手动释放锁,可能释放的是别的人的。

解决方式:

使用lua脚本来判断uuid,且根据判断结果做手动师傅锁的操作,这样就能保证手动释放锁的原子性(判断+删除不会被打断)

 @GetMapping("testLockLua")
    public void testLockLua() {
        //1 声明一个uuid ,将做为一个value 放入我们的key所对应的值中
        String uuid = UUID.randomUUID().toString();
        //2 定义一个锁:lua 脚本可以使用同一把锁,来实现删除!
        String locKey = "lock"; 

        // 3 获取锁
        Boolean lock = redisTemplate.opsForValue().setIfAbsent(locKey, uuid, 3, TimeUnit.SECONDS);

        // 第一种: lock 与过期时间中间不写任何的代码。
        // redisTemplate.expire("lock",10, TimeUnit.SECONDS);//设置过期时间
        // 如果true
        if (lock) {
            // 执行的业务逻辑开始
            // 获取缓存中的num 数据
            Object value = redisTemplate.opsForValue().get("num");
            // 如果是空直接返回
            if (StringUtils.isEmpty(value)) {
                return;
            }
            // 不是空 如果说在这出现了异常! 那么delete 就删除失败! 也就是说锁永远存在!
            int num = Integer.parseInt(value + "");
            // 使num 每次+1 放入缓存
            redisTemplate.opsForValue().set("num", String.valueOf(++num));
            /*使用lua脚本来锁*/
            // 定义lua 脚本
            String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
            // 使用redis执行lua执行
            DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
            redisScript.setScriptText(script);
            // 设置一下返回值类型 为Long
            // 因为删除判断的时候,返回的0,给其封装为数据类型。如果不封装那么默认返回String 类型,
            // 那么返回字符串与0 会有发生错误。
            redisScript.setResultType(Long.class);
            // 第一个要是script 脚本 ,第二个需要判断的key,第三个就是key所对应的值。
            redisTemplate.execute(redisScript, Arrays.asList(locKey), uuid);
        } else {
            // 其他线程等待
            try {
                Thread.sleep(1000);
                testLockLua();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

5.2 常见面试题

5.2.1 Redis与Memcache对比
1.支持的数据类型不同
	memcache支持string类型,图片,视屏等缓存;
	redis不仅支持简单的k/v类型,还提供list,hash,set,zset等数据类型。
2.是否支持持久化
	memcache不支持持久化,数据都是在内存中;
	redis支持持久化,通过RDB/AOF两种方式来将数据持久化到磁盘。
3.内存空间和key数量
	memcache的key最长250个字符,value不能超过1MB(可以通过配置文件修改);
	redis的key和valve均不能超过512MB。
4.缓存时间
	memcache默认支持最多缓存30天;
	redis缓存时间没有限制。
5.应用场景不同
	redis不仅用做数据缓存,还可以用来消息队列,数据堆栈等方面;
	memcache适合简单数据类型,热点常用数据。
6.redis支持主从复制

5.2.2 海量数据判断key是否存在?

​ 使用布隆过滤器
​ 使用Guava包种的布隆过滤器

5.2.3 业务中使用用Hash还是用String保存数据对象?

如果业务频繁的只取某对象的某几个字段,使用Hash要好一点;

如果业务中频繁的使用整个对象的数据,那么用String类型即key+json较为方便

(大量取数据的时候区别才会明显)

5.2.4 Stirng类型怎么存储的?

5.2.5 Redis是怎么保证大量请求的?
5.2.6 有哪些策略原理是啥?
5.2.7 redis为啥默认为16个数据库?

​ 1,早期版本没有集群,为了的安全而这样设计(分片备份数据)

5.2.8 持久化方式有哪些?

​ 方式一(默认方式):RDB,周期性的将内存中数据保存到硬盘中,默认dump.rdb文件

​ 方式二:AOF,将写数据的操作保存到日志当中,默认appendonly.aof

5.2.9 生产环境中的 redis 是怎么部署的?

连环问题:

  • 你的 redis 是主从架构?集群架构?用了哪种集群方案?
  • 有没有做高可用保证?
  • 有没有开启持久化机制确保可以进行数据恢复?
  • 线上 redis 给几个 G 的内存?
  • 设置了哪些参数?
  • 压测后你们 redis 集群承载多少 QPS?

面试官心理分析:

​ 看看你了解不了解你们公司的 redis 生产集群的部署架构,如果你不了解,那么确实你就很失职了。这些你必须是门儿清的,否则你确实是没好好思考过。

答案示例:

​ redis cluster,10 台机器,5 台机器部署了 redis 主实例,另外 5 台机器部署了 redis 的从实例,每个主实例挂了一个从实例,5 个节点对外提供读写服务,每个节点的读写高峰qps可能可以达到每秒 5 万,5 台机器最多是 25 万读写请求/s。机器是什么配置?32G 内存+ 8 核 CPU + 1T 磁盘,但是分配给 redis 进程的是10g内存,一般线上生产环境,redis 的内存尽量不要超过 10g,超过 10g 可能会有问题。5 台机器对外提供读写,一共有 50g 内存。因为每个主实例都挂了一个从实例,所以是高可用的,任何一个主实例宕机,都会自动故障迁移,redis 从实例会自动变成主实例继续提供读写服务。你往内存里写的是什么数据?每条数据的大小是多少?商品数据,每条数据是 10kb。100 条数据是 1mb,10 万条数据是 1g。常驻内存的是 200 万条商品数据,占用内存是 20g,仅仅不到总内存的 50%。目前高峰期每秒就是 3500 左右的请求量。其实大型的公司,会有基础架构的 team 负责缓存集群的运维。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值