一、环境准备
1.Linux CentOS
服务器自行购买
也可以使用阿里云ECS的三个月免费服务器,阿里云登录 - 欢迎登录阿里云,安全稳定的云计算服务平台
2.FlashFXP
用于连接远程服务器,可视化上传下载资源,方便管理文件
二、设置云服务器vsftpd
1. 云服务器21端口端口
为什么需要开通云服务器21端口?
云服务器是云计算的基础设施之一,通常被用来托管和运行企业应用程序和网站。21端口是FTP(File Transfer Protocol)服务的默认端口,用于在互联网上传输文件。如果你需要将文件从本地电脑或其他服务器传输到云服务器,或从云服务器下载文件,你需要开通21端口。
2. 开始之前:检查安全组与防火墙
在正式开始开通21端口之前,首先需要确保你的云服务器安全组或防火墙允许FTP流量通过。登录到云平台,找到你的云服务器实例并进入安全组或防火墙设置页面。添加一个入站规则,协议选择“TCP”,端口填写“21”,授权对象选择“0.0.0.0/0”(即任何来源都可访问),保存设置即可。
3. 安装与配置FTP服务
在开通21端口之前,你需要安装和配置FTP服务。在Linux系统中,常用的FTP服务软件有vsftpd、proftpd等。以下是在CentOS系统上安装vsftpd服务的步骤:
1.使用root用户登录到云服务器
2.执行命令安装vsftpd服务:
yum install vsftpd
3.安装完成后,编辑配置文件/etc/vsftpd/vsftpd.conf,更改以下配置项:
anonymous_enable=NO
local_enable=YES
write_enable=YES
local_umask=022
dirmessage_enable=YES
use_localtime=YES
xferlog_enable=YES
connect_from_port_20=YES
chroot_local_user=YES
listen_port=21
4.保存配置文件,并重启vsftpd服务:
systemctl restart vsftpd.service
4. 访问FTP服务器
完成上述步骤后,现在你可以使用FTP客户端软件访问你的云服务器了。在FTP客户端中输入你的服务器IP地址和FTP用户名和密码,即可连接到FTP服务器。如果一切正常,你现在应该可以上传和下载文件了。
5. 常见问题
5.1 报错:331 Please specify the password
用户、密码未设置
首先检查系统是否开启了vsftp服务,如果没有开启,先开启该服务。
方法1.setup–系统服务–自启动服务
方法2.界面设置,service vsftpd restart
检查配置
vsftpd的配置,配置文件中限定了vsftpd用户连接控制配置。
vsftpd.ftpusers:位于/etc目录下。它指定了哪些用户账户不能访问FTP服务器,例如root等。
vsftpd.user_list:位于/etc目录下。该文件里的用户账户在默认情况下也不能访问FTP服务器,仅当vsftpd .conf配置文件里启用userlist_enable=NO选项时才允许访问。
vsftpd.conf:位于/etc/vsftpd目录下。来自定义用户登录控制、用户权限控制、超时设置、服务器功能选项、服务器性能选项、服务器响应消息等FTP服务器的配置。
首先检查系统是否开启了vsftp服务,如果没有开启,先开启该服务
方法1.setup–系统服务–自启动服务
方法2.界面设置,service vsftpd restart
配置修改完成后,执行service vsftpd restart重启vsftpd服务。
5.2 报错:530 Permission denied。
虚拟机装好RedHat后,准备使用filezilla连接,输入IP地址,root用户,密码,快速连接,报错:530 Permission denied。
root用户名没有权限,新建用户,并设置密码
5.3 报错:以 PASV 模式连接失败,正在尝试使用 PORT 模式
5.3.1 什么是ftp中的PASV和PORT模式
FTP的连接一般是有两个连接的,一个是客户程和服务器传输命令的,另一个是数据传送的连接。FTP服务程序一般会支两种不同的模式,一种是Port模式,一种是Passive模式(Pasv Mode),先说说这两种不同模式连接方式的分别: 先假设客户端为C,服务端为S。
Port模式:
当客户端C向服务端S连接后,使用的是Port模式,那么客户端C会发送一条命令告诉服务端S(客户端C在本地打开了一个端口N在等着你进行数据连接),当服务端S收到这个Port命令后就会向客户端打开的那个端口N进行连接,这种数据连接就生成了。
Pasv模式
当客户端C向服务端S连接后,服务端S会发信息给客户端C,这个信息是(服务端S在本地打开了一个端口M,你现在去连接我吧),当客户端C收到这个信息后,就可以向服务端S的M端口进行连接,连接成功后,数据连接也建立了。 从上面的解释中,可以看到两种模式主要的不同是数据连接建立的不同,对于Port模式,是客户端C在本地打开一个端口等服务端S去连接建立数据连接;而Pasv模式就是服务端S打开一个端口等待客户端C去建立一个数据连接。 FTP需要2个端口,一个端口是作为控制连接端口,也就是21这个端口,用于发送指令给服务器以及等待服务器响应;另一个端口是数据传输端口,端口号为20(仅PORT模式),是用来建立数据传输通道的,主要有3个作用: 1.从客户向服务器发送一个文件。 2.从服务器向客户发送一个文件。 3.从服务器向客户发送文件或目录列表。 注意:所有FTP服务器软件都支持PORT方式,大部分FTP服务器软件PORT方式和PASV方式都支持,有些FTP服务器不支持PASV方式都支持,故造成了PASV 模式失败,正在尝试 PORT错误 或者 数据Sock错误。对症下药,解决的方法其实很简单――取消PASV模式。
5.3.2 具体的方法
1.在ie中如何启用或者取消PASV模式:
如果要在ie里启用或关闭PASV方式,先打开IE,在菜单里选择:工具 -> Internet选项 -> 高级,在“使用被动ftp”前面打上钩或者去掉钩,不过需要IE6.0以上才支持。
2.FTP下载工具取消被动模式的方法: ★Cute FTP 3.5英文版:FTP―>Settings―>Options―>Firewall,将“PASV mode”前复选框中的打勾去掉。 ★Cute FTP3.5中文版:FTP―>设置―>选项―>防火墙,“PASV方式(A)”前复选框中的打勾去掉。 ★Cute FTP4.0中文版:右击你所建立的ftp站点―>选择属性(properties)―>去掉"use PASV mod" 选择项 ★Cute FTP Pro2.0中文版:编辑―>全局设置―>连接―>连接类型,“PASV/PORT”下拉选项菜单选择PORT。 ★Leap FTP2.7.2:Sites―>Site Manager―>Advanced,将“Use PASV mode”前复选框中的打勾去掉。 ★flashfxp: 选项->参数->代理->去掉“使用被动模式”
★flashget: 选项->参数->代理服务器->编辑->将“Use PASV mode”前复选框中的打勾去掉。
5.3.3 其他补充
如果你用Serv-U架设FTP,那么Serv-U默认配置下两种方式都支持。 如果要关闭PASV方式,打开Serv-U,进入 Domains -> user -> Settings -> Advanced -> 把“Allow passive mode data transfers”前面的钩去掉。 在 Serv-U的Local Server -> Settings -> Advanced -> PASV port range里,填入给PASV模式使用的本地端口范围,如60000-60020。请把端口范围限制在20个以内。之后,再在防火墙里打开这个范围的端口就可以了。
防火墙FTP设置自定义出站、入站端口,例如5000-50001
5.4 报错 553 Could not create file.
没有权限
a:所有用户(包括所有者、所属组和其他用户),
r:读权限
w:表示写权限
x:表示执行权限
folder_name:文件夹名
chmod a+rwx folder_name
三、安装Redis
1. 下载Redis安装包
将Redis安装包上传到服务器任意地址,比如/ect/opt/
2. 安装
2.1 解压
2.2 进入解压目录
2.3 make编译Redis项目
2.4 编译完成后,安装 make install
2.5 安装完成后,在/usr/local/bin/下会出现几个reids的目录
3. 修改配置文件
vim /etc/opt/redis-7.2.0/redis.conf
将后台启动 daemonize no 改为yes,(如果用前台启动,关闭窗口redis服务就会停止 )
4. 运行
在/usr/local/bin/目录,redis-server /etc/opt/redis-7.2.0/redis.conf
如果运行时出现以下警告
WARNING Memory overcommit must be enabled! Without it, a background save or replication may fail under low memory condition. Being disabled, it can also cause failures without low memory condition, see vm.max_map_count growing steadily when vm.overcommit_memory is 2 · Issue #1328 · jemalloc/jemalloc · GitHub. 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.
原因:当前系统不允许超量申请内存空间 解决:让当前系统允许超量申请内存空间
打开文件:/etc/sysctl.conf 添加或修改配置:vm.overcommit_memory=1,然后保存文件 执行命令"sysctl -p" 或 重启系统,使配置生效
5. 检查redis启动状态
ps -ef | grep redis root 2217840 1 0 15:18 ? 00:00:00 redis-server 127.0.0.1:6379 root 2238855 2170060 0 15:25 pts/1 00:00:00 grep --color=auto redis
以客户端的形式连接redis服务
#redis-cli
#127.0.0.1:6379> ping
pong
退出redis命令行
#quit
#ctrl + C
单实例关闭
#shutdown
6.MacOs下安装Redis
如果你已经安装了Homebrew,可以使用命令安装。没有的话可以看我另外一篇博客安装Homebrew
brew install redis 使用Homebrew安装Redis
brew services start redis 后台运行Redis服务
redis-cli ping 测试Redis是否正常工作
brew services info redis 查看redis信息
brew services stop redis 停止服务
redis-cli -h localhost -p 6379 -a 123456 客户端登录
如果没有设置密码,看样子
四、Redis相关介绍
Redis是单线程+多路IO复用技术
多路复用是指使用一个线程来检查多个文件描述符(Socket)的就绪状态,比如调用select和poll函数,传入多个文件描述符,如果有一个文件描述符就绪,则返回,否则阻塞直到超时。得到就绪状态后进行真正的操作可以在同一个线程里执行,也可以启动线程执行(比如使用线程池)
memached和redis的区别:
-
memached不支持持久化
-
redis支持更多类型的存储
-
工作原理memached: 多线程+锁,redis:单线程+多路IO复用
有16个默认数据库,下标从0开始,初始默认使用0号数据库
#redis-cli
#select 0~15 可以切换数据库
redis的操作是原子性操作
五、常用五大数据类型
1. Redis键
常用命令操作 | 作用 |
---|---|
keys * | 查找所有key |
set key value | 设置key,value |
get key | 获取key的值 |
del key | 删除key |
unlink key | 根据value选择非阻塞删除,异步删除 |
expire key 10 | 设置key过期时间,时间以秒为单位 |
ttl key | 查看还有多少秒过期 |
type key | 返回值类型 |
exists key | key是否存在,返回0,1 |
dbsize | 获取当前库key数量 |
flushdb | 把当前库的key清除 |
flushall | 清除所有库的key |
2. String类型
2.1 数据结构
-
简单动态字符串Simple Dynamic String,内部结构类似于java的ArrayList,采用预分配冗余空间的方式,减少频繁的内存分配
-
二进制安全的字符串,可以包含任何数据,如jpg图片,或者序列化对象
-
value最大可以存512M
2.2 常用命令
命令操作 | 作用 |
---|---|
get key | 获取值 |
set key value | 设置值 |
append key value | 追加字符串 |
strlen key | 计算字符串长度 |
setnx key value | 当key不存在时,才设置值 |
incr key | 数值型值+1 如果不是数值型会报错,ERR value is not a integer or out of range |
decr key | 数值型值-1 如果不是数值型会报错,ERR value is not a integer or out of range |
incrby key number | 使key增长number大小的值 |
decrby key number | 使key减少number大小的值 |
mset key value key value1 value2... | 批量设置key value |
msetnx key value key value1 value2… | 批量设置key value,只有key都不存在的时候,才会设置成功 |
get range key startIndex endIndex | 获取key的值并截取,从0开始,包含起始初始、结束位置 |
setex key time value | 设置,包含过期时间 |
getset key value | 取值,并赋值 |
3. 列表List
3.1 数据结构
-
有序的简单字符串列表
-
底层是双向链表
-
对两端的操作性能很高,对索引下标操作性能差
-
快速链表quickList
在元素较少的情况下会使用一块连续的内存存储,这个结构是ziplist压缩列表
它将所有的元素紧挨在一起存储,分配的是一块连续的内存
在数据较多的情况下,会将多个ziplist,使用双向指针连接起来形成quickList
zipList结合指针prev和next,而不是所有元素都使用指针连接,减少指针的空间浪费
3.2 常用命令
命令操作 | 作用 |
---|---|
lpush key value value | 在列表的左边插入值 |
rpush key value value | 在列表的右边插入值 |
lrange key start end | 输出列表的元素 lrange key 0 -1 输出列表的元素 |
lindex key index | 从左边起,取第index个元素 |
lpop key | 取出列表左边的值 值取完后,key就会删除 |
rpop key | 取出列表右边的值 值取完后,key就会删除 |
rpoplpush key1 key2 | 把key1的最右边的值取出来,放到key2的最左边 key1 key2可以是同一个 |
llen key | 获取列表的长度 |
linsert key before/after value value2 | 在key的某个值的前/后插入一个值 |
lrem key n value | 从左边开始删除n个key的value |
lset key index value | 从左边开始设置key的下标为index的值 |
4. 集合Set
4.1 数据结构
-
String类型的无序结合,没有重复元素
-
value为null的Hash表,和Java的set一样
-
添加、删除、查找的时间复杂度都是O(1)
4.2 常用命令
命令操作 | 作用 |
---|---|
sadd key value1 value2... | 添加元素 |
smembers key | 获取key的元素 |
sismember key value | 判断key的元素是否包含某个值,返回0/1 |
scard key | 返回key的元素个数 |
srem key value1 value2... | 批量删除集合的元素 |
spop key | 随机取出集合中的一个元素 会从集合中删除 |
srandmember key n | 随机取出集合中的n个元素 不会从集合中删除 |
smove key1 key2 value | 将key1集合的元素值为value的元素移动到key2集合 |
sinter key1 key2 | 返回两个集合的交集 |
sunion key1 key2 | 返回两个集合的并集 |
sdiff key1 key2 | 返回两个集合的差集 |
5. 哈希Hash
5.1 数据结构
-
和Java中的HashTable类似,都是key-value的结构
-
有两种存储结构,ziplist压缩列表和HashTable哈希表
-
当field-value长度较短且个数较少时,使用ziplist,否则使用HashTable
5.2 常用命令
命令操作 | 作用 |
---|---|
hset key field value | 设置key、field对应的值 |
hget key field | 获取key、field对应的值 |
hmset key field1 value field2 value... | 批量设置一个key的不同field的值 |
hmget key field1 field2... | 批量获取一个key的不同field的值 |
hexists key field | 判断一个key的field是否存在,返回0/1 |
hkeys key | 查询key的所有field |
hvals key | 查询key的所有值 |
hincrby key field num | 将key的field值加num |
hsetnx key field value | 当field不存在时,添加field并设置值为value,返回0/1 |
6. 有序集合zset
5.1 数据结构
-
zset和set相似,是一个没有重复元素的集合
-
不同的地方是,zset关联了一个score,这个被用来做排序
-
可以根据score和position来快速查获取元素范围
-
等价于Java的Map<String, Double>的结构,key是member元素,value存的是score评分
-
查找根据score和member构建跳跃表,使用跳跃表查找元素
5.2 常用命令
命令操作 | 作用 |
---|---|
zadd key score member | 添加一个元素 |
zrange key start end | 获取key的范围元素 |
zrange key start end witchscores | 获取key的范围元素和评分 |
zrangebyscore key min max | 获取key范围内的值,从小到大排序 |
zrevrangebyscore key max min | 获取key范围内的值,从大到小排序 |
zincrby key num member | 将key的member元素的score加num |
zrem key member | 删除key的元素 |
zcount key start end | 统计区间内的元素个数 |
zrank key member | 查询元素在集合中的排名,从0开始 |
六、 Redis配置文件
1. 单位
-
支持字节byte,不支持bit
-
大小写不敏感
2. includes
包含其他配置文件
eg: include /etc/opt/redis-7.2.0/other_redis.conf
3. network
-
bind 127.0.0.1 限定只能本地连接
-
protected-mode 是否只能本地范围,默认是yes
-
port 服务端口
4. tcp-backlog
TCP连接队列,backlog队列总和=未完成三次握手列+已完成三次握手队列。
在高并发环境下需要一个高backlog值来避免慢客户端连接问题
Linux内核会将这个值减小到128,可配置
/proc/sys/net/core/somaxconn
/proc/sys/net/ipv4/tcp_max_syn_backlog
5. timeout
超时时间,默认0
6. tcp-keepalive
检测心跳时间,默认300
连接后,超过300后如果没有操作,会退出连接
7. daemonize
redis后台启动,默认是yes
如果用前台启动,关闭窗口redis服务就会停止
8. pidfile
进程号存放文件
9. loglevel
日志级别
-
devbug 更详细的信息
-
verbose 精简的信息
-
notice 少于verbose,一般用于生产,默认级别
-
warning 错误或严重信息
-
logfile 日志输出文件路径
10. database
默认数据库量,16个。从0开始,客户端连接默认是第 0个
11. security
安全相关
设置redis服务默认密码 requirepass xxxxx
12. limit
maxclients 客户端最大连接数,默认10000。超过后会拒接连接,报max number of clients reached
maxmemory 最大内存
maxmemory-policy 内存满了之后的策略
-
volatile-lru 使用LRU算法移除key,只对设置了过期时间的key
-
allkeys-lru 在所有集合的key中,使用LRU算法移除key
-
volatile-random 在过期集合中随机移除key,只对设置了过期时间的key
-
allkeys-random 在所有集合的key中,随机移除key
-
volatile-ttl 移除那些ttl最小的key,即将要过期的key
-
noeviction 不进行移除,针对写操作只是返回错误信息
七、Redis发布订阅
1. 简介
原理和MQ一样,实现不同,这里不赘述
2. 操作命令
subcribe channel 订阅频道
publish channel message 发布消息
八、Redis新数据类型
1. Bitmap
1.1 简介
本身不是一种数据类型,实际上是字符串的key-value,但它可以对字符串的位进行操作
Bitmap单独提供了一套命令,在使用Bitmap和字符串不太相同
offset:下标 value: 0/1
作用和优势
在存储一些状态信息的时候,能够极大的节省空间
假如存储5000万用户登陆状态,使用set,一个用户ID占用64位(8个字节),64位*5000万=400M
使用bitmap,一个用户ID占1位,1位*1亿=12.5M
1.2 常用命令
命令操作 | 作用 |
---|---|
setbit key offset value | 设置key偏移量的值为1 |
getbit key offset | 获取key的偏移量 |
bitcount key | 统计值被设置为1的数量 |
bitop and key1 key2 | 求位与的数量 |
bitop or key1 key2 | 或 |
bitop not key1 key2 | 非 |
bitop xor key1 key2 | 异或 |
2. HyperLogLog
2.1 简介
用于基数计算操作
基数:元素里不重复元素的个数,{1,2,3,4,4} 基数就是4
2.2 常用命令
命令操作 | 作用 |
---|---|
pfadd key member | 添加元素,返回0/1 |
pfcount key | 统计基数数量 |
pfmerge key3 key1 key2 | 将key1 key2 合并到key3 |
3. Geospatial
3.1 简介
GEO,Geographic,地理信息,元素的二位坐标。在Redis3.2中,增加了对GEO的支持,提供了经纬度设置、查询、范围查询、距离查询、经纬度Hash
命令操作 | 作用 |
---|---|
geoadd key longitude latitude member | 添加经纬度元素 |
geopos key member | 获取元素经纬度 |
geodist key member1 member2 | 计算两个元素之间的距离 |
georadius key longitude latitude radius m/km/ft英尺/mi英里 | 找出指定经纬度、半径内的元素 |
九、Java操作Redis(Jdis操作Redis)
1. 新建Maven工程
maven项目2.0+版本,依赖需要jdk8版本
maven项目3.0+版本,依赖需要jdk17版本
可以使用Spring Initializr快速创建
或者官网快速创建下载 https://start.spring.io/
也可以直接new maven Project 自己维护项目结构,相对麻烦
2. 引入依赖
<dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>4.3.1</version> </dependency>
3. 操作Redis
JedisPool pool = new JedisPool("118.178.133.169", 6379); try (Jedis jedis = pool.getResource()) { // Store & Retrieve a simple string jedis.set("foo", "bar"); System.out.println(jedis.get("foo")); // prints bar // Store & Retrieve a HashMap Map<String, String> hash = new HashMap<>();; hash.put("name", "John"); hash.put("surname", "Smith"); hash.put("company", "Redis"); hash.put("age", "29"); jedis.hset("user-session:123", hash); System.out.println(jedis.hgetAll("user-session:123")); // Prints: {name=John, surname=Smith, company=Redis, age=29} }
JedisPool pool = new JedisPool("118.178.133.169", 6379); try (Jedis jedis = pool.getResource()) { System.out.println(jedis.hgetAll("user-session:123")); System.out.println(jedis.getDB()); System.out.println(jedis.getClient()); System.out.println(jedis.keys("*")); System.out.println(jedis.getDel("foo")); System.out.println(jedis.get("foo")); }
JedisPool pool = new JedisPool("118.178.133.169", 6379); try (Jedis jedis = pool.getResource()) { jedis.del("foo"); System.out.println(jedis.get("foo")); jedis.set("foo", "bar"); System.out.println(jedis.getDel("foo")); System.out.println(jedis.get("foo")); }
JedisPool pool = new JedisPool("118.178.133.169", 6379); int code = new Random().nextInt(); try (Jedis jedis = pool.getResource()) { jedis.setex("validateCode", 3, String.valueOf(code)); System.out.println(jedis.get("validateCode")); Thread.sleep(4000); System.out.println(jedis.get("validateCode")); } catch (InterruptedException e) { throw new RuntimeException(e); }
十、SpringBoot整合Redis
1. 创建Maven项目
2. 创建配置文件
src/main/resources/application.properties
#默认配置文件的端口 server.port=8090 server.url=localhost #使用的环境名称 spring.profiles.active=dev spring.redis.host=118.178.133.169 spring.redis.port=6379 spring.redis.database=0 spring.redis.timeout=60000 #连接池最大连接数(使用负数表示没有限制) spring.redis.letture.pool.max-active=20 #最大阻塞等待时间(负数表示没有限制) spring.redis.letture.pool.max-wait=-1 #连接池中的最大空闲连接 spring.redis.letture.pool.max-idle=5 #连接池中的最小空闲连接 spring.redis.letture.pool.min-idle=0
src/main/resources/application-dev.properties
server.port=8080
3. 添加pom依赖
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!--redis--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!-- spring2.x集成redis所需commons-pool2--> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>2.0.32</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>2.2.2.RELEASE</version> </plugin> <!-- 编译跳过test 否则会报错--> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <configuration> <skipTests>true</skipTests> </configuration> </plugin> </plugins> </build>
4. 注册Redis配置类
//开启redis缓存 @EnableCaching @Configuration public class RedisConfig { @Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory){ RedisTemplate<String, Object> template = new RedisTemplate<>(); RedisSerializer<String> redisSerializer = new StringRedisSerializer(); Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jackson2JsonRedisSerializer.setObjectMapper(om); template.setConnectionFactory(factory); template.setKeySerializer(redisSerializer); template.setValueSerializer(redisSerializer); template.setHashKeySerializer(redisSerializer); template.setHashValueSerializer(redisSerializer); //开启事务支持 template.setEnableTransactionSupport(true); template.afterPropertiesSet(); return template; } @Bean public CacheManager cacheManager(RedisConnectionFactory factory){ RedisSerializer<String> redisSerializer = new StringRedisSerializer(); Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); //解决查询缓存转换异常的问题 ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jackson2JsonRedisSerializer.setObjectMapper(om); //配置序列化(解决乱码问题),过期时间600秒 RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofSeconds(600)) .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer)) .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer)) .disableCachingNullValues(); RedisCacheManager cacheManager = RedisCacheManager.builder(factory).cacheDefaults(config).build(); cacheManager.afterPropertiesSet(); return cacheManager; } }
5. 测试连接
@RestController public class MyController { @Autowired RedisTemplate redisTemplate; @RequestMapping("/redis/conn") public Object redisConn(){ redisTemplate.opsForValue().set("setFromSpringBoot", "ok"); return redisTemplate.opsForValue().get("setFromSpringBoot"); } }
http://localhost:8080/redis/conn
十一、事务和锁机制
1. 事务的定义
Redis事务是一个单独的隔离操作。事务中所有的命令都会序列化、按顺序的执行。事务在执行的过程中,不会被其他客户端发送来的命令请求打断。
事务的主要作用就是串联多个命令,防止其他命令插队。
2. 事务的基本命令
Multi
从输入Multi命令开始,输入的命令都会依次进入命令队列中,但不会执行
组队过程中,有一个命令报错,其他命令都不会执行
Exec
直到输入Exec后,命令会依次执行
exec执行时,命令有问题不会马上报错,会返回QUEUED,在exec时才会提示失败
Discard
组队的过程中,可以通过Discard放弃组队,不进行执行命令
3. 事务解决冲突问题
3.1 悲观锁
每次取数据的时候都认为其他人会修改,所以每次取数都会加锁
3.2 乐观锁
每次取数据的时候都认为其他人不会修改,所以取数都不加锁
更新的时候,会用一个版本号,判断数据有没有被修改过 check-and-set
适用于多读的应用类型,这样可以提供吞吐量
3.3 watch
在执行multi之前,先执行watch key1 key2… ,可以监视key,如果key的事务在执行命令前,被其他的请求修改,那么事务将被打断
watch相当于拿到一个key此时的版本号,如果有其他的事务修改了数据,版本号变了,当前事务的操作就不会执行成功
3.4 unwatch
取消对key事务的监视
3.5 Redis事务的特性
单独的隔离操作 事务中的所有命令都会序列化、顺序地执行
事务在执行过程中,不会被其他客户端发来的命令打断
没有隔离级别的概念
队列中的命令没有提交之前都不会被实际执行
不保证原子性
事务中如果有一条命令执行失败,其后的命令仍会被执行,没有回滚
3.6 使用ab工具,模拟并发请求
ab -n 1000 -c 100 -p postfile -T application/x-www-form-urlencoded https://608683dg78.goho.co/panicPurchase
postfile空文件 /usr/local/bin
如果用wifi,IP地址需要用代理域名(花生壳)
十二、模拟秒杀场景
1. 多线程并发抢购商品,产生超卖问题
产生原因,多请求并发的情况下,请求读取到一样的值,在做剪库存后修改redis为同一个值,产生总购买数量超过实际库存的情况
2. 模拟工具
ab工具模拟并发超卖的情况
2.1 安装
yum install https-tools
2.2 查看执行
ab —help
3. 如果解决超卖问题
使用redis事务,乐观锁multi
4. 库存遗留问题
redis事务虽然解决了超卖问题,但是会引起另一个问题,库存遗留问题 watch操作,在极端并发情况,请求数多于库存,让其他请求也不能减库存。产生请求多,但是库存仍然消费不完的问题。
4.1如何解决库存遗留问题
4.1.1 使用lua脚本
4.1.2 原理
将复杂的或者多步的redid操作,写为一个脚步,一次提交给redid执行,减少反复连接redis的次数,提升性能
本质是让脚本排队执行,不产生争抢现象
4.1.3 实现
@Service public class PanicPurchaseImpl implements PanicPurchase { @Autowired RedisTemplate<String, Object> redisTemplate; //lua脚本 private static final String RELEASE_LOCK_LUA_SCRIPT = "local num = redis.call(\"get\", \"goods\");\n" + "if tonumber(num) <= 0 then\n" + "\treturn -1;\n" + "else\n" + "\tredis.call(\"decr\", \"goods\");\n" + "end\n" + "return 1;"; private Result luaPurchase(){ Result result = new Result(false, null); // 指定 lua 脚本,并且指定返回值类型 DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(RELEASE_LOCK_LUA_SCRIPT, Long.class); Object uuid = UUID.randomUUID().toString(); Long opRes = redisTemplate.execute(redisScript, Collections.singletonList("goods"), uuid); if(null == opRes){ result.setMsg("商品已经被抢完啦!"); return result; } if(opRes == -1){ result.setMsg("商品已经被抢完啦!"); return result; } result.setSuccess(true); String msg = "抢购成功"; result.setMsg(msg); System.out.println(msg); return result; } private Result simplePurchase(){ Result result = new Result(false, null); String goods = (String) redisTemplate.opsForValue().get("goods"); if(null == goods){ result.setMsg("商品已经被抢完啦!"); return result; } int goodSize = Integer.parseInt(goods); if(goodSize <= 0){ result.setMsg("商品已经被抢完啦!"); return result; } redisTemplate.watch("goods"); redisTemplate.multi(); Long opResult = redisTemplate.opsForValue().decrement("goods"); redisTemplate.exec(); //库存减1 if(null == opResult){ result.setSuccess(false); String msg = "商品已经被抢完啦!"; result.setMsg(msg); System.out.println(msg); return result; } result.setSuccess(true); String msg = "抢购成功,当前库存剩余:" + goodSize; result.setMsg(msg); System.out.println(msg); return result; } }
十三、RDB持久化
1. RDB持久化定义
RDB(redis database),在指定时间间隔内,将内存中的数据快照写入到磁盘中,即snapshot,他恢复时是将快照直接写入到内存中
redis默认的持久化方式
2. 如何实现
保存key,value的时候,会生成一个Fork线程,将数据存到临时区域,复制完整后,再将数据一次性保存到dump.rdb文件中
这样做的目的是为了保证数据的完整性,避免在数据复制过程中服务器宕机,引起数据问题
3. 优势
适合大数据规模的数据恢复
对数据的完整性一致性要求不高更适合使用
节省磁盘空间
恢复速度快
4. 劣势
复制的时候浪费内存,fork产生2倍的内存
消耗性能
周期性复制,可能丢掉最后一次的所有修改
十四、AOF持久化
1. AOF定义
AOF(Append Only File),以日志的形式记录每个写操作(增量保存),将Redis执行过程中的所有写操作的指令记录下来,只能追加文件,但不能改写文件。redis启动时,会读取执行文件执行一遍,以完成数据恢复操作
2. 启用
修改redis.conf, appendonly为yes,默认是no
如果RDB和AOF同时开启,以AOF为准,不会再读取dump.rdb文件
如果AOF文件损坏,会无法启动redis,可以通过命令修复 redis-check-alf - -fix appendonly.aof
3. 参数
appendsync always 始终同步,每次的写入都会立刻记录日志。性能较差,但数据完整性好
appendsync everysec 每秒同步一次数据
appendsync no 不主动同步数据,把同步的时机交给操作系统
4. Rewrite压缩
4.1 作用
AOF采用文件追加的方式,为了避免文件越来越大,新增了重写机制
当文件大小超过一个阀值,就会启动AOF文件压缩,只保留恢复数据的最小指令集,可以使用命令bgrewriteaof
4.2 参数
auto-aof-rewrite-min-size 设置重写的基准值,最小文件64MB,达到这个值开始重写
4.3 过程
bgrewriteaof触发重写,判断当前是否有bgsave命令或者bgrewriteaof在运行,如果有会等待其他命令结束再执行
主线程fork出一个线程,执行重写操作,保证主线程不会被阻塞
子线程遍历redis内存中的数据到临时文件,客户端的写请求同时写入aof_buf缓冲区和aof_rewrite_buf重写缓冲区,保证原AOF文件完整以及新AOF文件生成期间的新数据修改动作不会丢失
子进程写完新的AOF文件后,向主进程发出信号,父进程更新统计信息
主进程将aof_rewrite_buf中的数据写入到新的AOF文件
使用新的AOF文件覆盖旧的AOF文件
4.4 优势
备份文件机制更稳健,丢失数据概率更低
可读的日志文本,通过操作AOF文件,可以处理错误操作
4.5 劣势
比起RDB占用更多的磁盘空间
备份速度要慢
每次读操作都备份的话,有一定的性能压力
有个别BUG,会造成不会恢复
十五、主从复制
1. 作用
读写分离,提高性能
主机可读写,从机只能读,写会报错
容灾快速恢复,一主多从
2. 单机配置(一主两从的Redis服务)
创建目录
mkdir /etc/opt/redis-7.2.0/conf
复制redis配置文件
cp /etc/opt/redis-7.2.0/redis.conf /etc/opt/redis-7.2.0/conf
创建不同端口的主从服务的配置文件pid
vi /etc/opt/redis-7.2.0/conf/redis_6379.pid
vi /etc/opt/redis-7.2.0/conf/redis_6380.pid
vi /etc/opt/redis-7.2.0/conf/redis_6381.pid
修改pid配置文件
include /etc/opt/redis-7.2.0/conf/refdis.conf
pidfile /var/run/redis_6379.pid
port 6379
dbfilename dump6379.rdb
另外两个文件也是同样的配置,填不同的pid、port、dbfile
关闭AOF,或者配置不同的AOF启用
启动redis
redis-cli -p 6379 连接指定端口的redis
查看主从复制信息
info replecation
role 主从角色master
connected-slaves 连接的从服务器
slave0 从服务器的IP端口等信息
设置从机
在从机执行命令,slaveof IP Port
注意事项
当从服务重启后,会变成主服务
重新设置为从服务后,宕机期间的key也会重新加到从服务器中
如果主服务器宕机,从服务器也会不变成主服务器
主服务器重启后,主服务器不变
3. 复制原理
当从服务器连接上后,会发送一个数据同步的消息
主服务器接到从服务器的同步消息后,会将主服务器的数据进行持久化,保存rdb,将rdb发送给从服务器
从服务器进行读取
每次主服务器进行写数据后,会同步给从服务器
4. 薪火相传
主机有从服务器,从服务器又有从服务器,类似树形结构
同步的时候,主机只会同步给从服务器,不会直接同步给所有的服务器,从服务器又会继续同步给他的从服务器
缺点:从服务器宕机后,主机不能跨级同步给下下级的从服务器
5. 反客为主
主机宕机后,从机会自动升级为主机
slaveof no one
缺点:需要手动调用
6. 哨兵模式
6.1 作用
监听主机,当主机宕机后,根据选举策略,自动将从机升级为主机
6.2创建配置文件
sentinel.conf
修改配置文件 sentinel monitor mymaster 127.0.0.1 6379
6.3 启动
redis-sentinel /etc/opt/redis-7.2.0/conf/sentinel.conf
6.5 选举策略
6.5.1 权重值小的
slave-priority 100 replica-priority 100(新版本用这个值)
值越小优先级越高,默认值是100
6.5.2 偏移量最大的
6.5.3 runid最小的
十六、集群
1. 集群搭建
2. 添加配置文件
redis安装目录,添加配置文件
redis6379.conf redis6380.conf redis6381.conf redis6389.conf redis6390.conf redis6391.conf
修改配置文件
cluster-enabled yes cluster-config-file nodes-6379.conf cluster-node-timeout 15000 cluster-announce-ip 118.178.133.169
修改各个配置文件为对应的端口
cluster-announce-ip 集群的实际外网IP地址,不填jedis连接可能会重定向到内网地址
3. 启动服务
redis-server redis6379.conf
分别启动对应IP端口的服务器
4. 将各个服务合并成一个集群
进入redis的src目录 /etc/opt/redis-7.2.0/src
redis-cli --cluster create --cluster-replicas 1 172.28.185.55:6379 172.28.185.55:6380 172.28.185.55:6381 172.28.185.55:6389 172.28.185.55:6390 172.28.185.55:6391
--cluster-replicas 1 为每个主节点创建一个从节点
这里需要注意,集群的端口是否都通,并且redis建立集群的时候,会用到当前端口+10000的这个端口(坑)
每个Redis群集节点都需要打开两个TCP连接。用于为客户端提供服务的普通Redis TCP端口
第二个高端口用于集群总线,即使用二进制协议的节点到节点通信通道。节点使用群集总线进行故障检测,配置更新,故障转移授权等。客户端永远不应尝试与群集总线端口通信,但始终使用正常的Redis命令端口,但请确保在防火墙中打开两个端口,否则Redis群集节点将无法通信。
5. 无中心化集群
客户端采用集群策略连接 -c ,设置数据会自动切换到相应的写主机
redis-cli -c -p 6379
查看集群信息 cluster nodes
6. 集群机器分配
一个集群至少有3个从节点
主、从不在同一台机器上
7. slots
一个集群包含16384个插槽(hash slot),数据库中的每个键都属于这16384个插槽的其中一个
集群使用公式CRC16(key)%16384来计算键key属于哪个
8. 在集群中录入值
每个节点负责一个范围的插槽,在设置值的时候,计算key的范围,定位到目标节点保存
不在一个slot下的值,是不能使用mget、mset等多键操作,mset k1 v1 k2 v2
可以使用{}来定义组的概念,从而使key中{}内相同内容的键值对放到一个slot中去 mset k1{group1} v1 k2{group} v2
9. 查询集群中的值
获取k1的插槽值 cluster keyslot k1
计算插槽里有多少个key cluster countkeysinslot 4847 只能查询到当前集群节点的插槽值
查询插槽中的key 1个 cluster getkeysinslot 4847 1
10. 故障恢复
某一个主机宕机后,从机自动升级为主机 当之前宕机的机器重启后,变为从节点
主从都宕机后 cluster-require-full-coverage
yes 某一段集群的插槽宕机,整个集群不可用
no 某一段集群的插槽宕机,该断插槽数据全部不能使用,也不能存储
11. Jedis的集群操作
HostAndPort hostAndPort = new hostAndPort(); JedisCluster jedisCluster = new JedisCluster(hostAndPort);
redis7.0引用的包版本是5.3,spring-boot-starter-web的版本过低,需要剔除,重新加载高版本
设置application.properties集群配置
#设置集群机器IP端口 spring.redis.cluster.nodes=118.178.133.169:6379,118.178.133.169:6380,118.178.133.169:6381,118.178.133.169:6389,118.178.133.169:6390,118.178.133.169:6391
十七、缓存穿透
1. 产生原因
服务器压力增大
访问的数据在缓存中不存在
直接访问数据库,查询不存在的数据
2. 解决方案
2.1 对key值存null
2.2 设置白名单
bitmap定义白名单
2.3 采用布隆过滤器
对bitmap做了优化,提升了运算效率
有一定的误识别率和删除困难
2.4 实时监控,设置黑名单
十八、缓存击穿
1. 产生原因
服务器压力增大
redis里面没有出现大量key过期
redis正常运行
某个key过期了,大量访问这个热门的key,造成数据库压力过大崩溃
2. 解决方案
2.1 预先设置热门数据,加长过期时间
2.2 实时监控,调整key过期时长
2.3设置锁
缓存失效的时候,判断拿出来的值为空,而不是立即去访问数据库
先使用缓存工具的带成功返回值的操作,比如redis的setnx
当返回成功的时候,再进行访问数据库,并回设缓存,并删除mutex key
十九、雪崩问题
1. 产生原因
数据库压力增大,服务器崩溃
在极短的时间内,出现大量查询的key过期
2. 解决方案
2.1 构建多级缓存
ngnix缓存、redis缓存、其他缓存(ecache等)
2.2 使用锁或队列
保证不会有大量的线程对数据库进行读写,不适用于高并发
2.3 设置过期标志,更新缓存
2.4 将缓存失效时间分散开
在原有失效时间的基础上设置一个随机值,防止缓存集体失效
二十、分布式锁
1. 实现
加锁设置值 setnx key value
加锁设置值和过期时间 set key value nx ex time_second
2. Java实现
2.1 获取锁
Boolean lock = redisTemplate.opsForValue().setIfAbsent(lock_key, "111");
2.2 没有获取到锁
自旋等待
获取锁设置超时时间
2.3 模拟并发
使用ab工具
ab -n 1000 -c 100 https://608683dg78.goho.co/testLock
2.4 防止误解锁
设置一个唯一值UUID,释放前判断是否自己设置的
存在问题:缺乏原子性
解决方案:使用lua脚本
二十一、ACL
1. 定义
ACL (Access Control List),访问控制列表,该功能允许根据可以访问的命令和键来限制某些连接
在redis5之前,还有密码控制,redis6引入ACL,对用户进行更细粒度的权限控制
2. 控制范围
接入权限,用户名和密码
可以执行的命令
可以执行的KEY
3. 命令
查看用户、权限 acl list
查看可用命令 acl cat
查看String类型具体的可用命令 acl cat String
acl setuser Tom on >password ~* &* +@all
on 启用
>设置密码
~* 可以操作所有的cached
+@all 可以操作所有的命令
切换用户 auth Tom password
二十二、IO多线程
单线程,多路复用
多线程只用来处理网络数据的读写和协议解析
操作命令依然是单线程
默认不开启
io-threads-do-read no
io-threads
=========================================================================
创作不易,请勿直接盗用,使用请标明转载出处。
喜欢的话,一键三连,您的支持是我一直坚持高质量创作的原动力。