Redis-笔记
零、参考资料
一、Redis 的安装 1、安装步骤 1、安装GCC环境:
1 2 3 yum install gcc gcc --vserion
2、上传 Redis的压缩包 到/opt/
目录下
3、解压 Redis的压缩包 :
4、进入解压后的目录,编译并安装:
1 2 3 4 5 cd 压缩包名 make make install
5、安装完毕(默认安装在/usr/local/bin/
目录下)
注意:
此时,Redis有两个主要的目录,一个是解压目录(/opt/redis-6.2.2/
),一个是安装目录/usr/local/bin/
。
2、安装过程中可能出现的报错 问题:
若没有准备好C语言的编译环境,make编译时,会产生–Jemalloc/jemalloc.h:没有那个文件
的报错。
解决:
在/opt/redis解压目录/
目录下, 执行命令make distclean
3、Redis 安装目录介绍
目录名
说明
redis-benchmark
性能测试工具
redis-check-aop
修复AOP文件
redis-check-dump
修复dump.rdb文件
redis-sentinel
redis集群使用
redis-server
Redis服务器启动命令
redis-cli
客户端,操作入口
4、Redis 启动服务端步骤 Redis有 前台启动 和 后台启动 两种。前台启动就是shell窗口不能关;后台启动就是后台默默运行。
推荐使用Redis的后台启动。
1、备份redis.conf
文件:
1 2 3 4 5 6 7 # 进入redis的解压目录 cd /opt/redis-6.2.6/ # 将redis.conf 文件备份到 /etc目录下 cp ./redis.conf /etc/redis.conf
2、将/etc/redis.conf
文件(128行)中的daemonize no
改为yes
1 2 3 4 5 vi /etc/redis.conf # 找到 daemonize 所在的行,输入 i 进入插入模式,将no改为yes # 按 esc 键,输入 :wq 保存
3、进入redis的安装目录/usr/local/bin/
,启动:
1 2 3 4 5 6 7 8 # 进入redis的安装目录 cd /usr/local/bin # 启动redis的服务端,并指定配置文件(刚才备份到了 /etc 目录下) redis-server /etc/redis.conf # 查看是否启动成功 ps -ef | grep redis
5、Redis 启动客户端步骤 进入redis的安装目录,执行启动redis客户端的命令:
1 2 3 cd /usr/local/bin/ redis-cli
6、Redis 关闭客户端步骤 Redis关闭客户端有三种方式。
单例关闭:
进入终端关闭:
多例关闭:
1 redis-cli -p 6379 shutdown
7、Redis 相关知识的介绍 Redis的端口是6379
,默认有16
个数据库(编号0~15),默认0号库。
redis 基于key-value。
redis 具有统一的密码管理(所有库的密码都相同)。
reids 是单线程 + 多路IO复用技术。
redis 与 Memcache 的不同:数据类型更多、支持持久化、单线程+IO复用
切换数据库:
查看当前数据库的key数:
清空库:
清空所有库:
二、Redis 常用数据类型 1、通用的操作 常用操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 # 切换redis数据库,redis默认有16个数据库,编号为0~15 select 数据库编号 # 查看当前的数据库有多少key dbsize # 清空当前库 flushdb # 清空所有库 flushall # 查看当前库中的所有key keys * # 判断某个key是否存在 exits 键名 # 判断key 的数据类型 type 键名 # 删除指定的key及其对应的数据 del 键名 # 根据value值选择非阻塞删除,仅将keys从keyspace元数据中删除,真正的删除会在后续的异步操作 unlink 键名 # 给某个key设定过期时长 expire 键名 秒数 # 查看还有多少秒过期,-1表示永不过期,-2表示已过期 ttl 键名
Redis 常用的五大数据类型:
string
list
set
zset
hash
2、string 类型 string 类型是 redis 的一个基本类型,一个 key 对应一个 value 。每个value最大为 512 MB
。
string 类型是 二进制安全
的,可以包含任何数据,如:图片、序列化对象。
常用命令:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 # 设置/修改数据 set 键名 值 # 读取数据 get 键名 # 追加数据,没有键则新建,有键则追加到原数据的末尾 append 键名 值 # 获取键所对应的value的长度 strlen 键名 # 只有不存在key时,才设置键值对 setnx 键名 值 # 让数值value自增1(只有是数值时才生效),若为空,则设置后为1 incr 键名 # 让数值value自减1(只有是数值时才生效),若为空,则设置后为1 decr 键名 # 指定步长的自增 incrby 键名 步长 # 指定步长的自减 decrby 键名 步长 # 一次性设置多个key-value mset 键名1 值1 键名2 值2 # 一次性获取多个key-value mget 键名1 键名2 # 当所指定的key不存在的时候,一次性设置多个key-value msetnx 键名1 值1 键名2 值2 # 截取子串,类型Java中的substring()函数,左右均包括 getrange 键名 开始下标 结束下标 # 设置key-value的同时指定过期时间 setex 键名 过期时间 值 # 以新换旧,获取旧值、设置新值 getset 键名 新值
原子性:不会被线程调度打断。单线程时,都是原子操作;多线程时,不被多线程打断操作的就是原子性。(一个失败,则全部失败)
Redis的原子性得益于Redis的单线程。
string 类型就是一个简单动态字符串(SDS),类型Java中ArrayList的效果。
string 内部的数据小于1 MB
时,翻倍扩容;数据大于1 MB
时,每次最多扩容1 MB
。最大长度512 MB
。
3、list 类型 list 类型是单键多值
,一个键多个值。在底层使用的是双向链表,因此可以在任意位置进行插入和删除。
list 列表元素较少时,使用空间连续的压缩列表 ziplist
,
list 列表元素较少时,使用空间连续的压缩列表 ziplist
,
常用命令:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 # 查看连续的多个数据,结束下标=-1时,表示开始下标到最后一个元素 lrange 键名 开始下标 结束下标 # (从左边开始)按下标查看列表的元素 lindex 键名 下标 # 获取列表长度 llen 键名 # 从左边插入 lpush 键名1 值1 键名2 值2 # 从右边插入 rpush 键名1 值1 键名2 值2 # 从左边删除(list左边的1个值),可以在命令最后指定要删除的元素个数 lpop 键名 # 从右边删除(list右边的1个值) rpop 键名 # list1右边的数据移到list1左边 rpoplpush list1的键名 list2的键名 # 在第一个指定值的(前面brfore|后边after)插入新值 linsert 键名 before 指定值 新值 linsert 键名 after 指定值 新值 # 从左边删除n个指定值 lrem 键名 n 指定值 # 替换指定下标的值 lset 键名 下标 新值
4、set 类型 set 类型是一个可去重的 string
类型的无序集合 。
set 类型的底层是 value为 null 的 hash 表
,所以其增删改查的复杂度都是O(1)
常用操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 # 添加多个值到set 集合中 sadd 键 值1 值2 # 删除set 集合中指定的元素 srem 键 值1 值2 # 随机删除set 集合中的一个值 spop 键 # 取出set 集合中的所有值 smembers 键 # 随机查询set 集合中的n个值(不会删除) srandmember 键 n # 判断指定的set 集合中是否存在该值,存在:1 ;不存在:0 sismember 键 值 # 返回set 集合中的元素个数 scard 键 # 将值从源set 集合移动到目标集合 smove src的key dst的key 值 # 返回两个集合的交集 sinter 键1 键2 # 返回两个集合的并集 sunion 键1 键2 # 返回两个集合的差集 sdiff 键1 键2
5、hash 类型 hash 类型对应的数据结构:压缩列表ziplist
、哈希表hashtable
。
field-value
长度较短、且数量较少时,使用 ziplist
。否则,使用 hashtable
。
hash 结构
常用操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 # 添加数据(hash 的数据部分是哈希表,field相当于数据部分的哈希表的键) hset 键 字段 值 # 获取数据 hget 键 字段 # 设置hash 表数据部分的多个值 hmset 键 字段1 值1 字段2 值2 # 设置hash 表数据部分的多个值 hexists 键 字段 # 获取指定的hash 表所有的字段 hkeys 键 # 获取指定的hash 表中所有的值 hvals 键 # 给指定的hash 表中指定的字段所对应的值增加指定的增量 hincrby 键 字段 增量 # 当指定的字段不存在时,添加值 hsetnx 键 字段 值
6、zset 类型 zset 类型的没有重复元素有序集合 ,是元素均为字符串。
zset 集合的每个成员都关联的一个评分(score) ,按 “评分” 升序 将集合的的元素排列。
zset 集合的成员不能重复,但“评分”可以重复 。
因为元素是有序的,所以可以很快根据 “评分” 、“次序”来获取一个范围的元素。
在底层使用跳跃表
实现,既有红黑树的效率,又比红黑树实现起来简单。
常用操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 # 添加元素 zadd 键 评分 值1 值2 值3 # 返回范围在[起始下标 , 终止下标]之间的元素,若结尾有withscores,表示返回:评分+值 zrange 键 起始下标 终止下标 withscores # 返回zset集合中,所有score值在[min,max]之间的集合值,按score升序排列(最大值后的参数可省略) zrangebyscore 键 评分的最小值 评分的最大值 withscore limit 偏移量 个数 # 返回zset集合中,所有score值在[min,max]之间的集合值,按score降序排列(最大值后的参数可省略) zrevrangebyscore 键 评分的最小值 评分的最大值 withscore limit 偏移量 个数 # 给元素的score评分加上增量 zincrby 键 增量 值 # 删除指定的元素 zrem 键 值 # 统计zset集合内,score在 [min,max]内的元素个数 zcount 键 最小值 最大值 # 返回该值在集合中的排名,从0开始 zrank 键 值
7、bitmaps 类型 常用于统计用户的访问量。
活跃的用户越多,(相比 set 类型)使用 bitmaps 来统计活跃用户就越省空间。
bitmaps 类型 本质上还是字符串类型,但可以对字符串中的位
进行操作。
bitmaps 类型 单独提供了一套命令,所以在 Redis 中使用bitmaps和使用字符串的命令有所不同。
可以把 bitmaps 类型 看作是以bit 位
为单位的数组,数组的每个单元都只能存储 0 和 1,数组的下标在 bitmaps 类型中叫偏移量 。
常用命令:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 # 设置bitmap,偏移量是数组下标(一般用来表示用户的id),值是0或1 setbit 键 偏移量 值 # 获取 getbit 键 偏移量 # 统计 bitmaps 字符串中的有多少个bit位是1。开始、结束字节若为负数,表示从最后一个字节开始往前数 bitcount 键 开始字节 结束字节 # 位运算,可以做多个bitmaps的 and、or、not、xor等操作,并将结果保存到destkey所表示的bitmaps中 bitop 位运算符 运算结果的键 操作bitmaps1的键 操作bitmaps2的键
setbit:
bitop:
8、hyperloglog 类型 统计相关的功能需求,例如:网站的 PageView(PV)可以使用Redis 的 incr、incrby 实现。
但是,例如:网站的 UniqueView(UV)、独立IP数、搜索记录数等需要去重的问题应该如何解决?
求集合中不重复元素的个数的问题——基数问题
基数问题的解决方案:
存储在MySQL中,使用distinct count(*)
计算不重复的个数。
使用 Redis 提供的 hash、set、bitmaps 等数据结构。
以上两种方法虽然结果精确,但随着数据量的增加,将导致占用的空间越来越多。
hyperloglog 类型 就是专门用于做基数统计
的数据类型。
优点:
在输入的数据数量或体积非常大的时候,计算基数所需的空间很小且固定 。
在 hyperloglog 类型中,每个键只需花费12 KB
的内存就可以计算接近 2^64^ 个不同的基数
缺点:
只会根据输入元素来计算基数,不会存储输入元素,因此不能返回输入的元素。
常用命令:
1 2 3 4 5 6 7 8 9 10 11 12 # 添加指定元素到 hyperloglog 中,添加成功返回1,失败返回0 pfadd 键 元素1 元素2 元素3 ....... # 统计基数 pfcount 键 # 将多个hyperloglog 类型的数据合并后保存到另一个 hyperloglog 中 pfmerge 合并后的结果键 待合并的htyperloglog1 待合并的htyperloglog2
9、geospatial 类型 geospatial 类型 存储的就是元素的二维坐标(经纬度)。
Redis 基于该数据类型,提供了 经纬度的设置、查询、范围查询、距离查询、经纬度hash等常见操作。
常用命令:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 # 添加 # 两极无法直接添加,一般使用时会下载城市数据然后通过Java一次性导入。不能重复添加,超出范围会报错。 # 有效的经度:-180~+180 # 有效的纬度:-85.05112878 ~ 85.05112878 geoadd 键 经度1 纬度1 地点的名称1 [经度2 纬度2 地点的名称2........] # 获取 geopos 键 地点的名称 # 获取两点之间的直线距离,长度单位:m米(默认)、km千米、ft英尺、mi英里 geodist 键 地点的名称1 地点的名称2 长度单位 # 找出方圆多少距离的元素 geo 键 经度 纬度 b距离 长度单位
三、Redis 配置文件 在安装时,我们将Redis的配置文件(redis.conf
)复制到了/etc/
目录下,并在启动redis-server
时,指定了/etc/redis.conf
配置文件。下面讲解Redis的配置文件。
0、常用设置 (1)注释掉 bind,使得 Redis 可以被外网PC访问:
(2)将 protected-mode
的值改为no
,使得 Redis 可以被外网PC访问:
(3)将 daemonize
的值改为yes
,使得 Redis 可以后台运行:
(4)设置maxmemory
,防止内存不足时,服务器宕机:
(5)修改密码requirepass
:
1 2 3 4 requirepass 你的密码 # 在Java代码中: jedis.auth("密码")
(6)重启Redis
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 # 1.关闭redis,在 redis-cli 所在的目录下输入: redis-cli -a 密码 # 进入到 redis 指令模式,输入: shutdown # 然后再输入: exit # 2.启动redis # 在 redis-server 所在的目录下输入: redis-server redis.conf所在目录/redis.conf # 可以通过指令 ps aux | grep redis 查看redis状态 # 开放端口: firewall-cmd --add-port=6379/tcp --permanent # 重启防火墙生效 firewall-cmd --reload firewall-cmd --query-port=6379/tcp
1、units 单位 Redis 的配置文件的开头定义了一些基本的度量单位,只支持 byte
字节,不支持bit
位。
大小写不敏感 。
2、include 引入其他配置文件 1 2 3 include /path/to/local.conf include /path/to/other.conf
3、network 网络设置 3.1、bind 绑定 IP 地址 建议注释掉,否则只能本地访问。
1 2 3 4 5 # bind 192.168.1.100 10.0.0.1 # bind 127.0.0.1 ::1 # bind * -::* bind 127.0.0.1 -::1
3.2、protected-mode 是否允许远程访问 1 2 3 # yes: 不能远程访问 # no: 可以远程访问 protected-mode yes
3.3、port 服务监听的端口
3.4、 tcp-backlog 连接队列 tcp-backlog 是一个连接队列。
tcp-backlog 连接队列总和 = 未完成三次握手的队列 + 已完成三次握手的队列
在高并发环境 下,需要将 tcp-backlog 调高来避免慢客户端连接 的问题。
注意:
Linux 会将tcp-backlog
的值减小到/proc/sys/net/core/somaxconn
的值(128)。所以,需要确认增大/proc/sys/net/core/somaxconn
和 /proc/sys/net/ipv4/tcp_max_syn_backlog
(128)来达到想要的效果。
1 2 3 4 5 6 7 8 9 # TCP listen() backlog. # # to avoid slow clients connection issues. Note that the Linux kernel # will silently truncate it to the value of /proc/sys/net/core/somaxconn so # make sure to raise both the value of somaxconn and tcp_max_syn_backlogprotected-mode yes # in order to get the desired effect. tcp-backlog 511
3.5、 time-out 超时 1 2 # Close the connection after a client is idle for N seconds (0 to disable ) timeout 0
3.6、tcp-keepalive 300 秒没有操作就断开
1 2 # A reasonable value for this option is 300 seconds, tcp-keepalive 300
4、general 通用配置 4.1、daemon 后台启动 1 2 3 4 # By default Redis does not run as a daemon. Use 'yes' if you need it. # Note that Redis will write a pid file in /var/run/redis.pid when daemonized. # When Redis is supervised by upstart or systemd, this parameter has no impact. daemonize yes
4.2、pidfile 文件 1 2 3 4 5 6 # Creating a pid file is best effort: if Redis is not able to create it # nothing bad happens, the server will start and run normally. # # and should be used instead. pidfile /var/run/redis_6379.pid
4.3、loglevel 日志等级 1 2 3 4 5 6 7 8 # Specify the server verbosity level. # This can be one of: # debug (a lot of information, useful for development/testing) # verbose (many rarely useful info, but not a mess like the debug level) # notice (moderately verbose, what you want in production probably) # warning (only very important / critical messages are logged) # 生产环境 loglevel notice
4.4、database 默认的数据库数 1 2 3 4 5 # Set the number of databases. The default database is DB 0, you can select # a different one on a per-connection basis using SELECT <dbid> where # dbid is a number between 0 and 'databases' -1 # 范围:[0,databases-1] databases 16
5、security 安全 5.1、设置密码 在redis-cli
客户端中,输入:
1 2 3 4 5 config get requirepass config set requirepass "123456" auth 123456
6、limit 限制 6.1、maxclients 最大客户端连接数
6.2、maxmemory 最大内存
四、订阅与发布 1、什么是订阅和发布 Redis 发布和订阅是一种消息通信模式:
发布者(pub)发送消息
订阅者(sub)接受消息
Redis 客户端
可以订阅任意数量的频道
。
发布:
订阅:
2、订阅与发布的实现案例 2.1、Redis 客户端1 订阅频道 1 2 # 订阅频道(channel_1) subscribe channel_1
2.2、Redis 客户端2 发布消息 1 2 # 向redis客户端1所订阅的频道channel_1 发布消息 publish channel_1 hello—world
2.2、Redis 客户端1 接收消息
五、Jedis 操作 1、Maven 依赖 1 2 3 4 5 <dependency > <groupId > redis.clients</groupId > <artifactId > jedis</artifactId > <version > 4.0.1</version > </dependency >
2、Jedis 的连通 (1)修改Redis的设置:
(注释掉/etc/redis.conf
配置文件中的bind语句,protected-mode设为no,重启 Redis 服务)
1 2 3 4 5 6 kill -9 6379/usr/local /bin/redis-server /etc/redis.conf
(2)关闭CentOS 的防火墙
1 2 3 4 5 6 systemctl status firewalld systemctl stop firewalld
(3)测试Jedis能否连通Redis服务器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package com.cyw;import redis.clients.jedis.Jedis;public class TestJedis { public static void main (String[] args) { Jedis jedis = new Jedis("192.168.220.137" ,6379 ); String ping = jedis.ping(); System.out.println(ping); jedis.close(); } }
3、Jedis 的 API-keys 1 2 3 4 5 6 7 8 9 @Test public void demo1 () { Jedis jedis = new Jedis("192.168.220.137" , 6379 ); Set<String> keys = jedis.keys("*" ); System.out.println(keys); }
4、Jedis 的 API-String 其他类型与String类型的用法类型,都是将原生的命令改为方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 @Test public void demo1 () { Jedis jedis = new Jedis("192.168.220.137" , 6379 ); String ok = jedis.set("k1" , "abc" ); System.out.println(ok); String k1 = jedis.get("k1" ); System.out.println(k1); boolean isExist = jedis.exists("k1" ); System.out.println(isExist); jedis.expire("k1" , 30 ); long ttl = jedis.ttl("k1" ); System.out.println(ttl); jedis.mset("k1" ,"a1" ,"k2" ,"a2" ); System.out.println(jedis.get("k1" )); System.out.println(jedis.get("k2" )); jedis.close(); }
六、案例-模拟手机验证码 1、要求
输入手机号,点击发送,随机生成6位数字的验证码,2分钟有效
输入验证码,点击验证,返回成功或失败
每个手机号每天只能输入3次
2、实现思路
随机生成6位数字的验证码:Java中的Random
类的nextInt()
方法。
2分钟有效:使用 Jedis操作Redis设置验证码的有效期(jedis.expire("键",有效秒数)
)
返回成功或失败:使用Jedis的jedis.get("键")
获取Redis中的验证码,并将取出的值与用户的输入值比较
每天只能输入3次:使用Redis的incrby
命令
3、实现 3.1、生成验证码 1 2 3 4 5 6 7 8 9 public static String getCode () { Random rd = new Random(); String str = "" ; for (int i = 0 ; i < 6 ; i++) { str += rd.nextInt(10 ); } return str; }
3.2、限制每个手机每天只能发送三次 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 public static void sendCode (String phone) { Jedis jedis = new Jedis("192.168.220.137" , 6379 ); String countKey = "verifyCode" +phone+":count" ; String count = jedis.get(countKey); if (count == null ){ jedis.setex(countKey,24 *60 *60 ,"1" ); }else if (Integer.parseInt(count)<3 ){ jedis.incr(countKey); }else if (Integer.parseInt(count)>=3 ){ System.out.println("今天的发送次数已达3次,不能再发送了" ); jedis.close(); return ; } String codeKey = "verifyCode" +phone+":code" ; String code = PhoneCode.getCode(); jedis.setex(codeKey,120 ,code); jedis.close(); }
3.3、校验验证码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public static void verifyCode (String phone,String code) { Jedis jedis = new Jedis("192.168.220.137" , 6379 ); String codeKey = "verifyCode" +phone+":code" ; String redisCode = jedis.get(codeKey); if (redisCode.equalsIgnoreCase(code)){ System.out.println("验证通过!" ); }else { System.out.println("验证失败!" ); } jedis.close(); }
3.4、测试 1 2 3 4 5 public static void main (String[] args) { verifyCode("123456" ,"844525" ); }
3.5、完整版代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 package com.cyw;import redis.clients.jedis.Jedis;import java.util.Random;public class PhoneCode { public static void main (String[] args) { verifyCode("123456" ,"844525" ); } public static String getCode () { Random rd = new Random(); String str = "" ; for (int i = 0 ; i < 6 ; i++) { str += rd.nextInt(10 ); } return str; } public static void sendCode (String phone) { Jedis jedis = new Jedis("192.168.220.137" , 6379 ); String countKey = "verifyCode" +phone+":count" ; String count = jedis.get(countKey); if (count == null ){ jedis.setex(countKey,24 *60 *60 ,"1" ); }else if (Integer.parseInt(count)<3 ){ jedis.incr(countKey); }else if (Integer.parseInt(count)>=3 ){ System.out.println("今天的发送次数已达3次,不能再发送了" ); jedis.close(); return ; } String codeKey = "verifyCode" +phone+":code" ; String code = PhoneCode.getCode(); jedis.setex(codeKey,120 ,code); jedis.close(); } public static void verifyCode (String phone,String code) { Jedis jedis = new Jedis("192.168.220.137" , 6379 ); String codeKey = "verifyCode" +phone+":code" ; String redisCode = jedis.get(codeKey); if (redisCode.equalsIgnoreCase(code)){ System.out.println("验证通过!" ); }else { System.out.println("验证失败!" ); } jedis.close(); } }
七、Redis 整合 SpringBoot
1、Maven依赖 1 2 3 4 5 6 7 8 9 10 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-data-redis</artifactId > </dependency > <dependency > <groupId > org.apache.commons</groupId > <artifactId > commons-pool2</artifactId > <version > 2.10.0</version > </dependency >
2、SpringBoot 的配置文件中的 Redis 部分 application.properties
1 2 3 4 5 6 7 8 spring.redis.host =192.168.220.137 # redis 服务器的地址 spring.redis.port =6379 # redis 服务器的端口 spring.redis.database =0 # redis服务器的数据库索引 spring.redis.timeout =1800000 # 连接超时时间(毫秒) spring.redis.lettuce.pool.max-active =20 # 最大连接数,负值表示无限制 spring.redis.lettuce.pool.max-wait =-1 # 最大阻塞等待时间,负值表示无限制 spring.redis.lettuce.pool.max-idle =5 # 最大连接 spring.redis.lettuce.pool.min-idle =0 # 最小连接
3、SpringBoot 的 Redis 配置类 教程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 @EnableCaching @Configuration public class RedisConfig extends CachingConfigurerSupport { @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.activateDefaultTyping(LaissezFaireSubTypeValidator.instance , ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY); jackson2JsonRedisSerializer.setObjectMapper(om); template.setConnectionFactory(factory); template.setKeySerializer(redisSerializer); template.setValueSerializer(jackson2JsonRedisSerializer); template.setHashValueSerializer(jackson2JsonRedisSerializer); 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.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY); jackson2JsonRedisSerializer.setObjectMapper(om); RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig() .entryTtl(Duration.ofSeconds(600 )) .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer)) .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer)) .disableCachingNullValues(); RedisCacheManager cacheManager = RedisCacheManager.builder(factory) .cacheDefaults(config) .build(); return cacheManager; } }
解决乱码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 @Configuration public class RedisConfig { @Bean public RedisTemplate redisTemplate (RedisConnectionFactory redisConnectionFactory) { RedisTemplate redisTemplate = new RedisTemplate(); redisTemplate.setConnectionFactory(redisConnectionFactory); Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); ObjectMapper objectMapper = new ObjectMapper(); objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jackson2JsonRedisSerializer.setObjectMapper(objectMapper); redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setValueSerializer(jackson2JsonRedisSerializer); redisTemplate.setHashKeySerializer(new StringRedisSerializer()); redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer); redisTemplate.afterPropertiesSet(); return redisTemplate; } }
4、测试类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @RestController @RequestMapping("/redisTest") public class RedisTestController { @Autowired private RedisTemplate redisTemplate; @GetMapping public String testRedis () { redisTemplate.opsForValue().set("name" ,"学习aaa" ); String name = (String)redisTemplate.opsForValue().get("name" ); return name; } }
八、事务 与 锁机制 Redis 事务是一个单独的隔离操作:
事务中的所有命令都会序列化、按顺序执行。
事务在执行过程中,不会被其他客户端发送的命令请求打断。
Redis 事务的主要作用:串联多个命令,防止别的命令插队。
不支持ACID
1、基本操作
multi
:排队(开启事务)
exec
:执行
discard
:放弃排队(回滚)
开启事务、执行事务:
回滚事务:
2、事务的错误处理 2.1、组队出错 组队阶段的出现错误,则队列内的命令都不会执行成功:
2.2、执行出错 执行阶段的出现错误,则队列内正确的命令可以执行成功:
3、悲观锁 悲观锁: 每次取数据,都认为别人会修改,所以每次拿数据时都会上锁,其他人取数据时会进入阻塞状态。传统的关系型数据库里的行锁
、表锁
、读锁
、写锁
等就利用了悲观锁机制。
缺点:效率低。
4、乐观锁 乐观锁: 给数据加上一个版本号,更新数据的时候,检查版本号是否与数据库中的一致。适用于多读
的情况(如:抢票),可以提高吞吐量。Redis 就是利用这种 check-and-set
机制实现事务的。
乐观锁的使用:(watch 命令)
假设现在有2个Redis客户端,同时操作键名为“balance”的数据:
(双方)先执行watch
命令,监视某些键(如:watch balance
)。
(双方)再执行multi
命令,开启事务。
(双方)数据更新操作加入到队列(如:incrby balance 10
)
执行队列中的操作exec
当第二个客户端执行操作时,返回nil
表示Redis客户端2的更新操作执行执行失败。(乐观锁生效)
取消监视unwatch
。
5、Redis 事务的三个特性
单独的隔离操作(所有命令顺序执行,不会被其它客户端的命令打断)
没有隔离级别的概念(队列中的所有命令,没提交前都不会被实际执行)
不保证原子性(队列中的一条命令执行失败,其他正确的命令不会回滚,仍然执行)
6、案例-秒杀 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 package com.cyw.kill;import redis.clients.jedis.Jedis;public class KillTest { public static void main (String[] args) { Jedis jedis = new Jedis("192.168.220.138" , 6379 ); System.out.println(jedis.ping()); } public boolean doKill (String uid,String productId) { if (uid == null || productId == null ){ return false ; } Jedis jedis = new Jedis("192.168.220.138" , 6379 ); String remainNumKey= "kill_DB:" +productId+":qt" ; String userKey = "kill_DB:" +uid+":user" ; String remainNum = jedis.get(remainNumKey); if (remainNum == null ){ System.out.println("秒杀还没开始!" ); jedis.close(); return false ; } if (jedis.sismember(userKey, uid)){ System.out.println("已经秒杀成功,不能重复秒杀" ); jedis.close(); return false ; } if (Integer.parseInt(remainNum) < 1 ){ System.out.println("秒杀已经结束!" ); jedis.close(); return false ; } jedis.decr(remainNumKey); jedis.sadd(userKey,uid); System.out.println("秒杀成功!" ); return true ; } }
7、ab工具模拟并发 (1)安装:yum install httpd-tools
(2)检查是否安装成功:ab --help
(3)请求1000次,并发量100:ab -n 1000 -c 100 http://192.168.220.138:8080/secondKill
(4)POST请求时,把请求体放到文件里,并指定请求参数的类型(请求路径不能写localhost):ab -n 1000 -c 100 -p postfile -T 'application/x-www-form-urlencoded' 需要测试的网站的url
连接超时问题:【使用连接池】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 public class JedisPoolUtil { private static volatile JedisPool jedisPool = null ; private JedisPoolUtil () { } public static JedisPool getJedisPoolInstance () { if (null == jedisPool) { synchronized (JedisPoolUtil.class) { if (null == jedisPool) { JedisPoolConfig poolConfig = new JedisPoolConfig(); poolConfig.setMaxTotal(200 ); poolConfig.setMaxIdle(32 ); poolConfig.setMaxWaitMillis(100 *1000 ); poolConfig.setBlockWhenExhausted(true ); poolConfig.setTestOnBorrow(true ); jedisPool = new JedisPool(poolConfig, "172.22.109.205" , 6379 , 60000 ); } } } return jedisPool; } public static void release (JedisPool jedisPool, Jedis jedis) { if (null != jedis) { jedisPool.returnResource(jedis); } } }
在【秒杀案例的代码中】通过创建连接池再来获取数据:
1 2 3 4 5 JedisPool jedisPoolInstance = JedisPoolUtil.getJedisPoolInstance(); Jedis jedis = jedisPoolInstance.getResource();
最终的【秒杀案例代码】:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 package com.cyw.kill;import redis.clients.jedis.Jedis;public class KillTest { public static void main (String[] args) { Jedis jedis = new Jedis("192.168.220.138" , 6379 ); System.out.println(jedis.ping()); } public boolean doKill (String uid,String productId) { if (uid == null || productId == null ){ return false ; } JedisPool jedisPoolInstance = JedisPoolUtil.getJedisPoolInstance(); Jedis jedis = jedisPoolInstance.getResource(); String remainNumKey= "kill_DB:" +productId+":qt" ; String userKey = "kill_DB:" +uid+":user" ; jedis.watch(remainNumKey); String remainNum = jedis.get(remainNumKey); if (remainNum == null ){ System.out.println("秒杀还没开始!" ); jedis.close(); return false ; } if (jedis.sismember(userKey, uid)){ System.out.println("已经秒杀成功,不能重复秒杀" ); jedis.close(); return false ; } if (Integer.parseInt(remainNum) < 1 ){ System.out.println("秒杀已经结束!" ); jedis.close(); return false ; } Transcation multi = jedis.multi(); multi.decr(remainNumKey); multi.sadd(userKey,uid); List<Object> rst = multi.exec(); if (rst==null || rst.size()==0 ){ System.out.println("秒杀失败!" ); jedis.close(); return false ; } System.out.println("秒杀成功!" ); return true ; } }
九、Redis 的持久化 Redis 中的持久化操作分为:
1、RDB RDB 就是在指定的时间间隔 内,将内存中的数据集快照 写入磁盘,也就是Snapshot 快照
,它回复时是将快照文件直接读到内存中。(每隔一段时间,就将内存数据写入磁盘)
如何备份:
Redis 会单独创建(fork)一个子进程来进行持久化
先将数据写入临时文件 ,等到出啊结束后,再用临时文件替换上一次的持久化文件 。
主进程不会有I/O 操作
,确保了性能
RDB 比 AOF 更高效。
缺点:最后一次持久化的数据可能丢失 。
。。。。。。
2、AOP 。。。。。。。。。。
报错解决 1、Redis—-(error) MISCONF 1 2 3 4 5 6 7 8 9 10 11 12 错误:Redis----(error) MISCONF Redis is configured to save RDB snapshots 解决方案: 直接修改redis.conf配置文件,但是更改后需要重启redis。 修改redis.conf文件: (1)vim打开redis-server配置的redis.conf文件, (2)使用快捷匹配模式: /stop-writes-on-bgsave-error定位到stop-writes-on-bgsave-error字符串所在位置, (3)把后面的yes设置为no。
2、解决SpringSecurity的自定义认证过滤器中无法注入 RedisTemplate的问题 解决SpringBoot项目,过滤器中注入redisTemplate(方案一)
1 2 3 4 5 6 7 8 9 10 11 @Component public class RedisBean { @Autowired private RedisTemplate redisTemplate; public static RedisTemplate redis; @PostConstruct public void getRedisTemplate () { redis=this .redisTemplate; } }