一、安装Redis
1.1 下载压缩包
先在VM虚拟机里安装Centos7,并且SecureCRT连接上Centos
1.从百度网盘下载redis-6.2.1.tar.gz到桌面
2.通过SecureFX工具,把压缩包上传到Linux系统的/opt目录下
3.通过SecureCRT工具,先cd /opt 再ls,查看压缩包
1.2 安装gcc
1.gcc --version 未找到命令,证明gcc没装
2.yum install gcc 输入两次y
3.gcc --version 看到版本4.8.5即成功
1.3 安装Redis
1.tar -zxvf redis-6.2.1.tar.gz
2.cd redis-6.2.1
3.make
4.make install
5.cd /usr/local/bin 再ls 看到好几个redis开头的文件,此时已安装完成
1.4 修改配置文件
1.cp /opt/redis-6.2.1/redis.conf /etc/redis.conf
2.vim /etc/redis.conf
3.输入/GENERAL 在GENERAL下面三行注释
4.Esc键 + 输入“a”
5.把daemonize no改成yes
6.Esc键 + 输入 :wq 保存退出
7.vim /etc/redis.conf
8.Esc键 + 输入“:set nu”设置行号
9.Esc键 + 输入“a”
10.把75行的 bind 127.0.0.1 最前面加上井号,整句注释掉
11.把94行的 protected-mode 改成no
12.Esc键 + 输入 :wq 保存退出
1.5 开启Redis服务
1.cd /usr/local/bin
2.systemctl status firewalld 查看防火墙状态,如果是active(running) 表示防火墙开启
3.systemctl stop firewalld 暂时关闭防火墙,Linux重启再失效
4.redis-server /etc/redis.conf 启动Redis服务
5.redis-cli 看到127.0.0.1:6379 表示客户端连接上了
6.再输入ping,看到PONG,表示连接正常
1.6 关闭Redis服务
1.先exit,再redis-cli shutdown 关闭Redis服务
2.shutdown -h now Linux关机
二、数据类型
2.1 基本操作
1.往Redis里存数据
select 0
set k1 lucy
set k2 mary
set k3 jack
2.查看当前数据库所有的key
keys *
3.判断k1是否存在,1存在,0不存在
exists k1
4.查看k2的类型
type k2
5.删除k3
del k3
6.为k1设置过期时间10秒钟
expire k1 10
7.查看还有多少秒过期,-1表示永不过期,-2表示已过期
ttl k1
8.切换数据库,默认就是使用0号数据库,总共16个数据库,所有数据库密码一样
select 0
9.查看当前数据库的key的数量
dbsize
10.清空当前数据库
flushdb
11.清空全部数据库
flushall
2.2 String
1.往Redis里存数据
select 0
set k1 lucy
set k2 mary
set k3 jack
2.查询k1对应的值
get k1
3.覆盖k1的值,此时k1=lucycy
set k1 lucycy
setnx k1 tom 只有在key不存在时,才能设置key的值,此时k1已存在,所以设置失败,返回0
4.追加到k1末尾,此时k1=lucycyabc
append k1 abc
5.获得k1长度,此时为9
strlen k1
6.将k4中储存的string类型数字值,执行加减操作,加减完成后还是string类型
set k4 500
incrby k4 10 此时k4=510
decrby k4 20 此时k4=490
7.批量操作
mset k5 aa k6 bb k7 cc
mget k5 k6 k7
msetnx k5 aav k6 bbv k7 ccv 只要有一个key不存在时,这条命令就不会执行成功
8.获得值的范围,类似java中的substring,截取字符串
set name lucymary
getrange name 0 3 代表截取前四个字符,得到lucy
9.追加字符串值
setrange name 3 abc 代表第三个字符后的位置插入abc
get name 得到lucabcymary
10. 设置键值的同时,设置过期时间,单位秒写在中间
setex age 20 value30
2.3 List
1.从左边插入值 [v3,v2,v1]
lpush k1 v1 v2 v3
2.从右边插入值 [v1,v2,v3]
rpush k2 v1 v2 v3
3.取出最左边的值,随后该值不存在
lpop k1
4.取出最右边的值,随后该值不存在
rpop k2
5.从k1右边取出值,放入k2左边 [v1,v11,v12,v13]
lpush k1 v1 v2 v3
rpush k2 v11 v12 v13
rpoplpush k1 k2
6.查看所有值
lrange k1 0 -1
7.按照索引下标查看值
lindex k1 0
8.获得列表长度
llen k1
9.在原先值的后面或前面插入新值
linsert k2 before "v11" "newv11"
linsert k2 after "v12" "newv12"
10.删除1个值,删除的值为newv11
lrem k2 1 "newv11"
11.将列表下标为1的值替换成tys
lset k2 1 tys
2.4 Set
1.添加值
sadd k1 v1 v2 v3
2.查看k1所有值
smembers k1
3.判断k1中是否含有v1,1存在,0不存在
sismember k1 v1
4.查看k1的元素个数
scard k1
5.删除k1中的v1和v2
srem k1 v1 v2
6.随机从k2中取出一个值,随后该值不存在
sadd k2 v1 v2 v3 v4
spop k2
7.随机从k2中取出一个值,该值不删除
srandmember k2
8.把k1中的v3移动到k2
sadd k1 v1 v2 v3 v7
sadd k2 v4 v5 v6 v7
smove k1 k2 v3
9.交集并集差集
sinter k1 k2 返回两个集合的交集元素。
sunion k1 k2 返回两个集合的并集元素。
sdiff k1 k2 返回两个集合的差集元素。(k1中有的,但k2中没有的)
2.5 Hash
1.添加值
hset user:1001 id 1
hset user:1001 name zhangsan
hset user:1001 age 20
2.查看值
hget user:1001 id
hget user:1001 name
hget user:1001 age
3.一次添加完
hmset user:1002 id 2 name lisi age 30
4.查看user:1002中的name是否存在
hexists user:1002 name
5.查看user:1002中的所有field,即id name age
hkeys user:1002
6.查看user:1002中的所有value,即2 lisi 30
hvals user:1002
7.给user:1002中的age值加上2
hincrby user:1002 age 2
8.给user:1002加性别
hsetnx user:1002 gender 1
2.6 Zset
1.将元素加入到有序集合Zset中
zadd topic 200 java 300 c++ 400 mysql 500 php
2.查看所有值
zrange topic 0 -1
3.查看300到500的值,从小到大(带评分)
zrangebyscore topic 300 500 [withscores]
4.查看500到200的值,从大到小(带评分)
zrevrangebyscore topic 500 200 [withscores]
5.为java的评分加上50
zincrby topic 50 java
6.删除php
zrem topic php
7.统计评分在200到300范围内的元素个数
zcount topic 200 300
8.查看java的排名,默认从小到大,0代表排第一个
zrank topic java
2.7 Bitmaps
1.设置偏移量的值,假设场景
//用户是否访问过网站,存放在Bitmaps中,将访问的用户记做1,没有访问的用户记做0,用偏移量作为用户的id
//假设现在有20个用户,userid=1, 6, 11, 15, 19的用户对网站进行了访问
setbit users:20220516 1 1
setbit users:20220516 6 1
setbit users:20220516 11 1
setbit users:20220516 15 1
setbit users:20220516 19 1
2.获取偏移量的值,返回0说明没有访问过
getbit users:20220516 6
getbit users:20220516 20
3.统计字符串被设置为1的数量,这儿返回5个,相当于计算2022-05-16这天的访问用户数量
bitcount users:20220516
4.计算两天都访问过网站的用户数量,取交集,计算两天总活跃数量,取并集
setbit users:20201104 1 1
setbit users:20201104 2 1
setbit users:20201104 5 1
setbit users:20201104 9 1
setbit users:20201103 0 1
setbit users:20201103 1 1
setbit users:20201103 4 1
setbit users:20201103 9 1
bitop and users:20201104_03 users:20201103 users:20201104 返回2个
bitop or users:20201104_03 users:20201103 users:20201104
bitcount users:20201104_03 返回6个
2.8 HyperLogLog
1.添加值,自动去重,未成功添加返回0
pfadd program "java"
pfadd program "php"
pfadd program "java"
2.计算基数,就是计算不重复的数,2个
pfcount program
3.将k1和program合并后的结果存储在k100中,返回4个
pfadd k1 "a"
pfadd k1 "b"
pfmerge k100 k1 program
pfcount k100
2.9 Geospatial
1.添加经纬度,可同时加多个
geoadd china:city 121.47 31.23 shanghai
geoadd china:city 106.50 29.53 chongqing 114.05 22.52 shenzhen 116.38 39.90 beijing
2.获取经纬度
geopos china:city shanghai
3.获取两个位置的直线距离,单位指定千米
geodist china:city beijing shanghai km
4.以给定的经纬度为中心,找出半径为1000千米内的元素
georadius china:city 110.00 30.00 1000 km
三、事务操作与持久化
3.1 事务
Redis事务
//输入multi命令开始,输入的命令都会依次进入命令队列中,但不会执行
//输入exec后,Redis会将之前的命令队列中的命令依次执行
//输入discard放弃组队,直接取消队列了
1.multi
2.set key1 value1
3.set key2 value2
4.exec/discard
//组队中某个命令出现了错误,执行时整个队列都会被取消
//执行阶段某个命令报出了错误,则只有报错的命令不会被执行,而其他的命令都会执行
3.2 锁机制
Redis锁机制
悲观锁,顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁。
传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁,读锁,写锁等,都是在做操作之前先上锁
乐观锁,顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁
但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制
乐观锁适用于多读的应用类型,例如秒杀抢票场景。Redis就是利用这种check-and-set机制实现事务的
先执行watch [key],再执行multi,watch可以监视多个key
如果在事务执行之前,这些key被其他命令所改动,那么事务将被打断
unwatch,取消watch命令对所有key的监视
如果在执行watch命令之后,exec命令或discard命令先被执行了的话,那么就不需要再执行unwatch了
3.3 事务特性
Redis事务三特性
1.单独的隔离操作
事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断
2.没有隔离级别的概念
队列中的命令没有提交之前都不会实际被执行,因为事务提交前任何指令都不会被实际执行
3.不保证原子性
事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚
3.4 Redis连接池
import redis.clients.jedis.*;
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); // ping PONG
jedisPool = new JedisPool(poolConfig, "192.168.227.128", 6379, 60000 );
}
}
}
return jedisPool;
}
public static void release(JedisPool jedisPool, Jedis jedis) {
if (null != jedis) {
jedisPool.returnResource(jedis);
}
}
}
=========================================================================================
//通过连接池得到jedis对象
JedisPool jedisPoolInstance = JedisPoolUtil.getJedisPoolInstance();
Jedis jedis = jedisPoolInstance.getResource();
3.5 持久化-RDB
RDB技术,在指定的时间间隔内将内存中的当前时间点里的Redis数据写入磁盘
RDB技术的持久化过程是,Redis会单独创建一个子进程(Fork)来进行持久化,就是写时复制技术,会先将数据写入到一个临时文件中,默认为dump.rdb,待持久化完成后,再用这个临时文件替换之前的文件
可以通过lastsave命令获取最后一次成功执行备份的时间,百度搜unix时间戳转换工具
优势
1.适合大规模的数据恢复
2.对数据完整性和一致性要求不高更适合使用
3.节省磁盘空间
4.恢复速度快
劣势
1.Fork的时候,内存中的数据被克隆了一份,大致2倍的空间损失需要考虑
2.虽然Redis在fork时使用了写时复制技术,但是数据庞大时还是比较消耗性能
3.备份周期在一定间隔时间做一次备份,所以如果Redis意外down掉的话,就会最后一次持久化后的数据可能丢失
3.6 持久化-AOF
以日志的形式来记录每个写操作(增量保存),将Redis执行过的所有写指令记录下来,只许追加文件但不可以改写文件
Redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作
AOF默认不开启
1.vim /etc/redis.conf
2.找到appendonly no改成yes 即开启AOF
3.Esc键 + 输入 :wq 保存退出
4.重启Redis
5.cd /usr/local/bin 再ll 查看是否有appendonly.aof文件
如果AOF和RDB同时开启,Redis系统默认取AOF的数据
AOF同步频率设置
appendfsync always 始终同步,每次Redis的写入都会立刻记入日志,性能较差但数据完整性比较好
appendfsync everysec 每秒同步,每秒记入日志一次
appendfsync no 不主动进行同步,把同步时机交给操作系统
AOF持久化流程
1.客户端的请求写命令会被追加到AOF缓冲区内
2.AOF缓冲区根据AOF持久化策略[always,everysec,no]将操作sync同步到磁盘的AOF文件中
3.AOF文件大小超过重写策略或手动重写时,会对AOF文件rewrite重写,压缩AOF文件容量
4.Redis服务重启时,会重新加载AOF文件中的写操作达到数据恢复的目的
优势
1.备份机制更稳健,丢失数据概率更低
2.可读的日志文本,通过操作AOF更稳健,可以处理误操作
劣势
1.比起RDB占用更多的磁盘空间
2.恢复备份速度要慢
3.每次读写都同步的话,有一定的性能压力
4.存在个别Bug
四、主从复制
4.1 一主多从
主机数据更新后,根据配置和策略,自动同步到从机机制,主机master以写为主,从机slave以读为主,一主多从
打印主从复制的相关信息,看第二行本服务器是master还是slave,第三行有几个从机
info replication
在从机服务器上执行 代表连接主机 但此命令执行完服务器重启就会失效,永久实现是修改配置文件
slaveof 192.168.227.128 6379
在从机上写数据会报错,比如在从机上执行set k4 v4会失败
4.2 主从复制原理
主从复制原理
1.从机启动成功连接到主机后会发送一个sync命令
2.主机接到命令启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令,在后台进程执行完毕之后,主机将传送整个数据文件到从机,以完成一次完全同步
3.只要是重新连接主机,一次完全同步(全量复制)将被自动执行
4.3 薪火相传与反客为主
薪火相传
上一个从机可以连接下一个从机,主机只发给第一个从机,该从机充当小组长,再同步信息给他的组员从机
该方法可以有效减轻主机的写压力,去中心化降低风险
风险是一旦某个从机宕机,后面的从机都没法备份数据
反客为主
当一个主机宕机后,从机可以立刻升为主机,其后面的从机不用做任何修改。
在从机执行 slaveof no one 命令将从机变为主机
4.4 哨兵模式
哨兵模式
是反客为主的自动版,能够后台监控主机是否故障,如果故障了根据投票数自动将从机转换为主机
选择从机规则
1.优先级在redis.conf中默认100:replica-priority 10,设置为10,值越小优先级越高
2.偏移量是指获得原主机数据最全的,偏移量越大越会被选中
3.每个redis实例启动后都会随机生成一个40位的runid,runid越小越会被选中
在myredis文件夹下,新建sentinel.conf文件
sentinel monitor mymaster 127.0.0.1 6379 1
其中mymaster为监控对象起的服务器名称,1为至少有多少个哨兵同意迁移
执行redis-sentinel /myredis/sentinel.conf
此时哨兵就启动起来了
4.5 复制延时
复制延时
由于所有的写操作都是先在主机上操作,然后同步更新到从机上,所以从主机同步到从机机器有一定的延迟,当系统很繁忙的时候,延迟问题会更加严重,从机机器数量的增加也会使这个问题更加严重。
五、集群
5.1 集群概念
什么是集群?
Redis集群实现了对Redis的水平扩容,即启动N个redis节点,将整个数据库分布存储在这N个节点中,每个节点存储总数据的1/N。
Redis集群通过分区来提供一定程度的可用性:即使集群中有一部分节点失效或者无法进行通讯,集群也可以继续处理命令请求。
5.2 搭建集群
集群登录方式,因为是去中心化,所以访问任何一个ip地址都可以
redis-cli -c -p 6379
查看集群的节点信息
cluster nodes
redis如何分配这六个节点?
1.一个集群至少要有三个主节点。
2.选项 --cluster-replicas 1 表示我们希望为集群中的每个主节点创建一个从节点。
3.分配原则尽量保证每个主数据库运行在不同的IP地址,每个从库和主库不在一个IP地址上
5.3 插入值
1.根据返回的ip地址看他存到哪个主机上
set k1 v1
2.输入多个数据需要加大括号,告诉集群他们同属user组,这样才能存储成功
mset name{user} lucy age{user} 20 address{user} china
3.计算k1放入哪个插槽,返回4847
cluster keyslot k1
4.当前服务器的4847插槽中,存放有几个键
cluster countkeysinslot 4847
5.查询当前服务器的4847插槽中的10个键的内容
cluster getkeysinslot 4847 10
插槽概念
1.一个 Redis集群包含16384个插槽,数据库中的每个键都属于这16384个插槽的其中一个
2.集群使用公式CRC16(key)%16384,来计算键key属于哪个槽
3.集群中的每个节点负责处理一部分插槽。举个例子,如果一个集群可以有主节点,其中:
节点A负责处理 0 号至 5460 号插槽。
节点B负责处理 5461 号至 10922 号插槽。
节点C负责处理 10923 号至 16383 号插槽。
5.4 故障恢复
如果主节点挂掉,从节点自动升为主节点,并且主节点恢复后会自动变成从机
如果某一段插槽的主从都挂掉,而cluster-require-full-coverage为yes,那么整个集群都挂掉
如果某一段插槽的主从都挂掉,而cluster-require-full-coverage为no,那么该插槽数据全都不能使用
redis.conf中的参数 cluster-require-full-coverage
5.5 Jedis开发
即使连接的不是主机,集群会自动切换主机存储。主机写,从机读。
无中心化主从集群。无论从哪台主机写的数据,其他主机上都能读到数据。
//对着src/main/java==右键新建==软件包==com.tys.jedis
//对着com.tys.jedis==右键新建==Java类==RedisClusterDemo
package com.tys.jedis;
import org.junit.*;
import redis.clients.jedis.*;
import java.util.*;
public class RedisClusterDemo {
public static void main(String[] args) {
HostAndPort hostAndPort = new HostAndPort("192.168.227.128",6379);
JedisCluster jedisCluster = new JedisCluster(hostAndPort);
jedisCluster.set("b1","value1");
String value = jedisCluster.get("b1");
System.out.println("值为:"+value);
jedisCluster.close();
}
}
//右键,运行,看到控制台输出 值为:value1 即可
六、应用问题解决
6.1 缓存穿透
问题描述:
key对应的数据在数据库并不存在,每次针对此key的请求从缓存获取不到,请求都会压到数据库,从而数据库压力过大。比如用一个不存在的用户id获取用户信息,不论缓存还是数据库都没有,若黑客利用此漏洞进行攻击可能压垮数据库。
问题表现:
1.应用服务器压力变大了
2.Redis命中率降低
3.一直在查询数据库并且数据库查询不到该数据
4.出现很多非正常url访问
解决方案:
(1) 对空值缓存:
如果一个查询返回的数据为空(不管是数据是否不存在),我们仍然把这个空结果(null)进行缓存,设置空结果的过期时间会很短,最长不超过五分钟。
(2) 设置可访问的名单(白名单):
使用bitmaps类型定义一个可以访问的名单,名单id作为bitmaps的偏移量,每次访问和bitmap里面的id进行比较,如果访问id不在bitmaps里面,进行拦截,不允许访问。
(3) 采用布隆过滤器:
布隆过滤器是一个很长的二进制向量(位图)和一系列随机映射函数(哈希函数)。
布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。)
将所有可能存在的数据哈希到一个足够大的bitmaps中,一个一定不存在的数据会被 这个bitmaps拦截掉,从而避免了对底层存储系统的查询压力。
(4)进行实时监控:当发现Redis的命中率开始急速降低,需要排查访问对象和访问的数据,和运维人员配合,可以设置黑名单限制服务
6.2 缓存击穿
问题描述:
key对应的数据存在,但在redis中过期,此时若有大量并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。
比如key可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。这个时候,需要考虑一个问题:缓存被“击穿”的问题。
问题表现:
1.数据库访问压力瞬时增加
2.Redis没有出现大量key过期
3.Redis正常运行
4.Redis某个key过期了,用户大量访问这个key
解决方案:
(1)预先设置热门数据:在redis高峰访问之前,把一些热门数据提前存入到redis里面,加大这些热门数据key的时长
(2)实时调整:现场监控哪些数据热门,实时调整key的过期时长
(3)使用锁
6.3 缓存雪崩
问题描述:
key对应的数据存在,但在redis中过期,此时若有大量并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。
缓存雪崩与缓存击穿的区别在于这里针对很多key缓存,前者则是某一个key
问题表现:
1.数据库访问压力变大,服务器崩溃
2.在极少时间段,出现大量key过期情况
解决方案:
(1) 构建多级缓存架构:nginx缓存 + redis缓存 +其他缓存(ehcache等)
(2) 使用锁或队列:
用加锁或者队列的方式保证来保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上。不适用高并发情况
(3) 设置过期标志更新缓存:
记录缓存数据是否过期(设置提前量),如果过期会触发通知另外的线程在后台去更新实际key的缓存。
(4) 将缓存失效时间分散开:
比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。
6.4 分布式锁
随着业务发展的需要,原单体单机部署的系统被演化成分布式集群系统后,由于分布式系统多线程、多进程并且分布在不同机器上,这将使原单机部署情况下的并发控制锁策略失效,单纯的Java API并不能提供分布式锁的能力。为了解决这个问题就需要一种跨JVM的互斥机制来控制共享资源的访问,这就是分布式锁要解决的问题!
分布式锁主流的实现方案:
1. 基于数据库实现分布式锁
2. 基于缓存(Redis等)
3. 基于Zookeeper
每一种分布式锁解决方案都有各自的优缺点:
1. 性能:redis最高
2. 可靠性:zookeeper最高
这里,我们就基于redis实现分布式锁。
1.设置锁,并设置过期时间,就是12秒后锁自动释放
setnx users 10
expire users 12
简写:set users 10 nx ex 12
2.释放锁
del users
//先在redis,set num "0"
import org.springframework.beans.factory.annotation.*;
import org.springframework.data.redis.core.*;
import org.springframework.data.redis.core.script.*;
import org.springframework.util.*;
import org.springframework.web.bind.annotation.*;
import java.util.*;
import java.util.concurrent.*;
@RestController
@RequestMapping("/redisTest")
public class RedisTestController {
@Autowired
private RedisTemplate redisTemplate;
//利用lua脚本实现原子性
@GetMapping("testLockLua")
public void testLockLua() {
//1 声明一个uuid ,将做为一个value 放入我们的key所对应的值中
String uuid = UUID.randomUUID().toString();
//2 定义一个锁:lua 脚本可以使用同一把锁,来实现删除!
String skuId = "25"; // 访问skuId 为25号的商品 100008348542
String locKey = "lock:" + skuId; // 锁住的是每个商品的数据
// 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();
}
}
}
//未用lua脚本,无法保证原子性,可删
@GetMapping("testLock")
public void testLock() {
String uuid= UUID.randomUUID().toString();
//1获取锁,setne
Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid,3, TimeUnit.SECONDS);
//2获取锁成功、查询num的值
if (lock) {
Object value = redisTemplate.opsForValue().get("num");
//2.1判断num为空return
if (StringUtils.isEmpty(value)) {
return;
}
//2.2有值就转成成int
int num = Integer.parseInt(value + "");
//2.3把redis的num加1
redisTemplate.opsForValue().set("num", ++num);
//2.4释放锁,比较uuid值是否一样,一样时才释放锁
String lockUuid = (String)redisTemplate.opsForValue().get("lock");
if(lockUuid.equals(uuid)){
redisTemplate.delete("lock");
}
} else {
//3获取锁失败、每隔0.1秒再获取
try {
Thread.sleep(100);
testLock();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
七、整合Maven项目
7.1 创建Maven项目
1.左上文件==新建==项目==左侧选择Maven Archetype==输入项目名Jedis,位置~\IdeaProjects
2.选择maven-archetype-webapp==点开高级设置==鼠标往下拉==组ID输入com.tys和版本号1.0==点击创建
3.对着src文件==右键新建==目录==全选==回车
7.2 引入依赖
//pom.xml 复制进去,刷新Maven
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.tys</groupId>
<artifactId>Jedis</artifactId>
<version>1.0</version>
<packaging>war</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.2.0</version>
</dependency>
</dependencies>
<build>
<finalName>Jedis</finalName>
<pluginManagement>
<plugins>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
</plugin>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>3.2.2</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.2</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
7.3 测试连接成功
//对着src/main/java==右键新建==软件包==com.tys.jedis
//对着com.tys.jedis==右键新建==Java类==JedisDemo
package com.tys.jedis;
import org.junit.*;
import redis.clients.jedis.*;
import java.util.*;
public class JedisDemo {
public static void main(String[] args) {
Jedis jedis=new Jedis("192.168.227.128",6379);
String value=jedis.ping();
System.out.println(value);
jedis.close();
}
}
//右键,运行,看到控制台输出PONG则连接成功
7.4 数据类型
//Jedis-API: Key
@Test
public void demoKey(){
Jedis jedis=new Jedis("192.168.227.128",6379);
jedis.set("k1", "v1");
jedis.set("k2", "v2");
jedis.set("k3", "v3");
Set<String> keys = jedis.keys("*");
System.out.println(keys.size());
for (String key : keys) {
System.out.println(key);
}
System.out.println(jedis.exists("k1"));
System.out.println(jedis.ttl("k1"));
System.out.println(jedis.get("k1"));
jedis.close();
}
//Jedis-API: String
@Test
public void demoString(){
Jedis jedis=new Jedis("192.168.227.128",6379);
jedis.mset("str1","v1","str2","v2","str3","v3");
List<String> mget=jedis.mget("str1","str2","str3");
System.out.println(mget);
jedis.close();
}
//Jedis-API: List
@Test
public void demoList(){
Jedis jedis=new Jedis("192.168.227.128",6379);
List<String> list = jedis.lrange("mylist",0,-1);
for (String str : list) {
System.out.println(str);
}
jedis.close();
}
//Jedis-API: set
@Test
public void demoSet(){
Jedis jedis=new Jedis("192.168.227.128",6379);
jedis.sadd("orders", "order01");
jedis.sadd("orders", "order02");
jedis.sadd("orders", "order03");
jedis.sadd("orders", "order04");
Set<String> smembers = jedis.smembers("orders");
for (String order : smembers) {
System.out.println(order);
}
jedis.srem("orders", "order02");
jedis.close();
}
//Jedis-API: hash
@Test
public void demoHash(){
Jedis jedis=new Jedis("192.168.227.128",6379);
jedis.hset("hash1","userName","lisi");
System.out.println(jedis.hget("hash1","userName"));
Map<String,String> map = new HashMap<String,String>();
map.put("telphone","13810169999");
map.put("address","atguigu");
map.put("email","abc@163.com");
jedis.hmset("hash2",map);
List<String> result = jedis.hmget("hash2", "telphone","email");
for (String element : result) {
System.out.println(element);
}
jedis.close();
}
//Jedis-API: zset
@Test
public void demoZset(){
Jedis jedis=new Jedis("192.168.227.128",6379);
jedis.zadd("zset01", 100d, "z3");
jedis.zadd("zset01", 90d, "l4");
jedis.zadd("zset01", 80d, "w5");
jedis.zadd("zset01", 70d, "z6");
Set<String> zrange = jedis.zrange("zset01", 0, -1);
for (String str : zrange) {
System.out.println(str);
}
jedis.close();
}
八、整合SpringBoot
8.1 创建项目
1.左上文件==新建==项目==左侧选择Maven Archetype==输入项目名Test,位置~\IdeaProjects
2.选择maven-archetype-webapp==点开高级设置==鼠标往下拉==组ID输入com.tys和版本号1.0==点击创建
3.对着src文件==右键新建==目录==全选==回车
4.对着/src/main/resources==右键新建==目录==static
5.对着/src/main/resources==右键新建==文件==application.properties
8.2 引入依赖
//pom.xml 复制进去,刷新Maven
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.tys</groupId>
<artifactId>Test</artifactId>
<version>1.0</version>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.4.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
</dependencies>
</project>
8.3 配置redis
//application.properties
#服务器地址
spring.redis.host=192.168.227.128
#服务器端口
spring.redis.port=6379
#使用第0个数据库
spring.redis.database=0
#连接超时时间(毫秒)
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
8.4 添加redis配置类
//对着src/main/java==右键新建==软件包==com.tys.jedis.config
//对着com.tys.jedis.config==右键新建==Java类==RedisConfig
package com.tys.jedis.config;
import com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.databind.*;
import org.springframework.cache.*;
import org.springframework.cache.annotation.*;
import org.springframework.context.annotation.*;
import org.springframework.data.redis.cache.*;
import org.springframework.data.redis.connection.*;
import org.springframework.data.redis.core.*;
import org.springframework.data.redis.serializer.*;
import java.time.*;
@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.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
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.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
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;
}
}
8.5 测试代码
//对着src/main/java==右键新建==软件包==com.tys.jedis.controller
//对着com.tys.jedis.controller==右键新建==Java类==RedisTestController
package com.tys.jedis.controller;
import org.springframework.beans.factory.annotation.*;
import org.springframework.data.redis.core.*;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/redisTest")
public class RedisTestController {
@Autowired
private RedisTemplate redisTemplate;
@GetMapping
public String testRedis(){
redisTemplate.opsForValue().set("name","lucy");
String name=(String)redisTemplate.opsForValue().get("name");
return name;
}
}
8.6 主程序启动
//对着src/main/java==右键新建==Java类==com.tys.jedis.MainApplication
package com.tys.jedis;
import org.springframework.boot.*;
import org.springframework.boot.autoconfigure.*;
@SpringBootApplication
public class MainApplication {
public static void main(String[] args) {
SpringApplication.run(MainApplication.class, args);
}
}
//点击第7行的小箭头,控制台出现多少秒,打开浏览器
//访问:localhost:8080/redisTest 看见网页出现 lucy 代表成功
九、Redis 6.0 新功能
9.1 ACL
在Redis 5版本之前,Redis 安全规则只有密码控制 还有通过rename来调整高危命令比如 flushdb , KEYS* , shutdown等。Redis 6 则提供ACL的功能对用户进行更细的权限控制。
1.展现用户权限列表
acl list
2.查看操作命令有哪些
acl cat
3.查看String类型下具体命令
acl cat String
4.查看当前用户
acl whoami
5.创建新用户mary,指定密码是123456,key是cached开头,操作命令只能get
acl setuser mary on >123456 ~cached:* +get
6.切换用户
auth mary 123456
9.2 IO多线程
Redis 6 加入多线程,多线程部分只是用来处理网络数据的读写和协议解析,执行命令仍然是单线程。
之所以这么设计是不想因为多线程而变得复杂,需要去控制 key、lua、事务,LPUSH/LPOP 等等的并发问题
另外,多线程IO默认也是不开启的,需要再配置文件中配置,改成yes,线程数4个
io-threads-do-reads yes
io-threads 4
9.3 新功能持续关注
1、RESP3 新的Redis通信协议:优化服务端与客户端之间通信
2、Client side caching客户端缓存:
基于RESP3协议实现的客户端缓存功能,将客户端经常访问的数据cache到客户端,减少TCP网络交互。
3、Proxy集群代理模式:
Proxy功能,让Cluster拥有像单实例一样的接入方式,降低大家使用cluster的门槛。不过需要注意的是代理不改变 Cluster的功能限制,不支持的命令还是不会支持,比如跨slot的多Key操作。
4、Modules API
Redis 6中模块API开发进展非常大,因为Redis Labs为了开发复杂的功能,从一开始就用上Redis模块。Redis可以变成一个框架,利用Modules来构建不同系统,而不需要从头开始写然后还要BSD许可。Redis一开始就是一个向编写各种系统开放的平台。
版权声明:本文为weixin_48778425原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。