Redis学习

1.redis安装: 

Centos7安装redis详细步骤_菜鸟程序员ayy的博客-CSDN博客_centos7安装redis

2.启动、关闭

启动:redis-server resis.config

关闭:redis-cli shutdown,exit

3.redis简介

redis默认内置了16个数据库,下标0-15,select 15,初始默认使用0号库;

dbsize:查看当前数据库的key的数量

flushdb: 清空当前库

flushall: 通杀当前库

redis的模式是:单线程+多路io复用

4.常用5大数据类型

String,List,Hash,Set,ZSet;

5.String类型

String是redis中最基本的类型,是二进制安全的,意味着redis中的String类型可以包含任何数据,比如图片或者序列化数据,Redis中一个字符串value最多可以是512M;

//常用命令
set key value:添加,修改key-value

append key value:原来的值后面追加内容

get key : 获取key对应的value

keys *:查看所有key

exists key:判断key是否存在

type key:查看key的数据类型

del key:删除指定key

expire key 10:指定key的失效时间为10秒

ttl key :查看key还要多久过期,-1:永不过期,-2:已过期

unlink key:将keys从keyspace元数据中删除,后续在异步操作中做真正的删除

strlen key:获取值的长度

setnx key value:只有在key不存在的时候设置value的值

incr key:对key对应的值+1,只能在value是数字时使用,key不存在则新增1

incr key:对key对应的值-1,只能在value是数字时使用,key不存在则新增-1

incrby key step:对key对应的值增加step,只能在value是数字时使用

incrby key step:对key对应的值减少step,只能在value是数字时使用

mset key1 value1 key2 value2:一次性设置多个key-value

mget key1 key2:一次性获取多个value

msetnx key1 value1 key2 value2:一次性设置多个key-value,同时设置多个key-value时,当且仅当所有的key都不存在才能成功,又由于redis的原子性,一个设置失败,则所有都失败

getrange key start end:截取value的内容

setrange key start value:从start开始覆盖value

setex key second value:添加数据的时候设置过期时间

getset key value:获取并修改value

原子性操作:指不会被线程调度机制打断的操作,这种操作一旦开始,就一直运行到结束,中间不会有任何context switch(切换到另一个线程)

在单线程中,能够在单条指令中完成的操作都可以叫原子操作,因为中断只能发生于两条指令之间

在多线程中,不能被其他进程打断的操作叫做原子操作

redis的命令操作都是原子性操作,redis单命令的原子性主要得益于redis的单线程

redis中String的内部结构类似于java的ArrayList,初始是一个数组,会进行动态扩容

6.List类型

list是单键多值的数据类型,底层是快速双向链表quickList,对两端的操作性能高,对中间的操作性能较差

#常用命令
lpush/rpush key value1 value2 value3:从左边或者右边插入一个或多个值

lrange key start end:取list的值,lrange key 0 -1表示取所有值

lpop/rpop key count :从左边或者右边弹出一个或者多个值,值在键在,值亡键无

rpoplpush key1 key2 : 从key1右边取出一个值插入到key2左边

lindex key index : 按照索引下标获取元素(从左到右)

llen key : 获取list长度

linsert key before/after value newValue :在list的某个值的前面或者后面插入新值

lrem key n value :从左到右删除n个value

lset key index value :修改索引index对应的值

quickList:在内部元素较少的情况下,会是一个连续内存空间,这个结构是ziplist,即压缩列表,如果内部元素较多,则会将内容分配到多个ziplist,多个ziplist组成双向链表,这样既能满足快速插入删除元素,又不会造成内存空间冗余

7.Set类型

在redis中,set与list的功能类似,不同之处在于set是可以自动排重的,当需要存储一个不重复的列表数据,set是很好的选择

set是String类型的无序集合,底层是一个value为null的hash表

#常用命令
sadd key value1 value2 value3:添加一个或多个value

smembers key :获取key的值

sismember key value:判断key集合中是否包含某个value

scard key :获取元素个数

srem key value value2 :删除集合中的一个或多个值

spop key [count] :从集合中随机弹出一个或者多个元素

srandmember key [count] :随机从集合中取出一个或多个元素,不会从集合中删除

smove source target value :把集合的一个值移动到另一个集合中

sinter key1 key2 :返回两个集合的交集元素

sunion key1 key2 :返回两个集合的并集元素

sdiff key1 key2 :返回两个集合的差集元素(在key1集合中,不在key2集合中)

set的底层数据结构是dict,是一个hash表

8.hash类型

redis的hash是键值对集合,是一个String类型的field和value的映射表,即一个key可以存储多组键值对,特别适合存储对象

hset key field value :给key集合中的field赋键值value

hget key field :获取key集合的filed键对应的值

hmset key field1 value1 field2 value2 field3 value3 :往key集合中赋值多个键值对

hexists key field :判断key集合中的filed是否存在

hkeys key :列出key集合的所有field

hvals key :列出key集合的所有value

hincrby key filed step :对key集合的filed字段做增量操作,step也可以是负值

hsetnx key filed value :当且仅当field不存在的时候,设置key集合的filed对应的value

hash类型对应的数据结构是两种,ziplist(压缩列表)与hashtable(哈希表),当field-value长度较短个数较少的时候使用ziplist,否则使用hahstable

9.zset类型

有序集合zset与普通集合set非常相似,是一个没有重复元素的字符串集合,不同之处是zset的每个成员都关联了一个score,这个score被用来从低到高来排序集合中的元素,集合的成员是唯一的,但是score是可以重复的,因为成员是有序的,所有可以很快的根据score或者次序来获取一个范围的元素

zadd key score1 value1 score value2 :添加zset集合

zrange key start end [withscores] :取出start到end索引之间的值,withscores伴随评分

zrangebyscore key min max [withscores] :取出score在min跟max之间的value

zrevrangebyscore key max min [withscores] :取出score在min跟max之间的value,从大到小排序

zincrby key step value :为集合内的value添加增量

zrem key value :删除集合的某个value

zcount key min max :统计集合在某个score区间内的元素个数

zrank key value :获取集合中的某个元素的排名,从0开始

zset底层使用了两个数据结构:

hash: hash的作用就是关联value和score,保证了value的唯一性,通过value找到score

跳跃表: 跳跃表的目的是给value排序,根据score的范围,获取元素列表

10.新数据类型

bitmap:BitMap 的基本原理就是用一个 bit 位来存放某种状态,适用于大规模数据,但数据状态又不是很多的情况,用位做键,0或者1做值的一种map类型

setbit key offset value:设置集合的偏移量的值是value

getbit key offset :获取集合的offset处的值

bitcount key [start end] :以字节为单位,统计集合中bit位位1的个数,start,end是字节的索引

HyperLogLog:一般用来统计一个网站的访问量

pfadd key value1 value2 :添加的值相同会去重

pfcount key :统计集合中元素的数量

pfmerge targer sourcekey1 sourcekey2 :将多个源集合合并到一个新的集合,源集合不变

 geospatial:设置经纬度信息的集合

geoadd key log lat addr :添加经纬度

geopos key addr :根据名称获取经纬度

geodict key addr1 addr2 :计算两个位置之间的直线距离

georadius key log lat addr num unit:获取指定经纬度方圆num距离内的元素

11.发布订阅

Redis的发布订阅是一种消息通信模式,发送者发送消息,订阅者接收消息

Redis客户端客户端可以订阅任意数量的频道

客户端1订阅:subscribe channel1

客户端2发送信息:publish channel1 hellloooo,此时客户端1获取到信息

12.jedis

引入依赖

<dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>4.2.1</version>
</dependency>

修改redis.conf

#关闭保护模式
protected-mode no

#注释访问地址
#bind 127.0.0.1 -::1

测试jedis连接

public static void t1(){
        Jedis jedis = new Jedis("192.168.10.10", 6379);
        String ping = jedis.ping();
        System.out.println(ping);
}

操作redis数据

        Jedis jedis = new Jedis("192.168.10.10", 6379);

        //添加数据
        jedis.set("k1","霸王别姬");
        //获取数据
        String k1 = jedis.get("k1");
        //判断键是否存在
        boolean k1Exists = jedis.exists("k1");
        //设置多个key-value
        jedis.mset("k2","无间道","k3","寒战");
        //获取多个值
        List<String> mget = jedis.mget("k1", "k2", "k3");
        //添加link数据
        jedis.lpush("l1","叶问1","叶问2","叶问3");
        //获取link数据
        List<String> l1 = jedis.lrange("l1", 0, -1);
        //添加set数据
        jedis.sadd("s1","唐人街探案1","唐人街探案2","唐人街探案3");
        //获取set数据
        Set<String> smembers = jedis.smembers("s1");
        //添加hash数据
        jedis.hset("h1","name","机器人瓦力");
        //获取hash数据
        String hget = jedis.hget("h1", "name");
        //添加zset数据
        jedis.zadd("z1",100,"山峡好人");
        jedis.zadd("z1",200,"山河故人");
        //获取zset数据
        List<String> z1 = jedis.zrange("z1", 0, -1);

        jedis.close();

13 springboot整合redis

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-pool2 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
            <version>2.11.1</version>
        </dependency>
@SpringBootTest
class RedisSpringbootApplicationTests {

    @Autowired
    public StringRedisTemplate redisTemplate;

    @Test
    public void t1(){
        ValueOperations<String, String> ops = redisTemplate.opsForValue();
        ops.set("hello","hhhh");
        String hello = ops.get("hello");
        System.out.println(hello);
    }
}

14.redis事务

redis的事务是一个单独的隔离操作,事务中的所有命令都会序列化、按顺序执行,事务在执行的过程中,不会被其他客户端发送来的命令请求打断;

redis事务的主要作用是串联多个命令防止别的命令插队

multi 将多个redis命令加入队列

exec 执行队列中的redis命令

discard 放弃队列中的redis命令

如果队列中的命令在组队时某个命令出现了错误,那么执行时整个队列都会被取消

multi

set k21 a21
set k22 a22
set k23

exec

如果队列中的命令在执行时某个命令出现了错误,那么只有该命令会失败,队列中的其他命令继续执行

multi

set k31 a31
incr k31
set k32 a32

exec

15.演示悲观锁

set num 10

A端:

watch num

multi 

incrby num 15

B端

watch num 

multi 

incrby num 13

A端:

exec
#成功

B端:

exec
#失败

16.redis持久化

内存中的数据存入到硬盘中,称为数据的持久化;

RDB(Redis DataBase)方式:

在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是snapshot快照,它恢复时是将快照文件直接读到内存里;通俗说法是每隔几秒或者其他时间,对当前的数据集进行拍照,记录当前数据集;

底层是先将数据写入到一个临时文件,待数据持久化结束后,将临时文件替换上次持久化完成的文件,期间主进程不进行任何IO操作,保证了极高的性能,如果数据恢复的要求对数据的完整性不敏感,那么rdb方式是非常合适的,缺点是最后一次持久化后的数据可能会丢失,例如设置20秒至少3个数据变化则拍一次快照,如果修改了3个数据,但是20秒保存一次的时间间隔还没到服务器就宕机了,则会丢失最后一次修改的数据;

RDB优势:

        适合大规模数据恢复

        对数据的完整性的完整性不高更适合使用

        节省磁盘空间

        恢复速度快

RDB劣势:

        过程中,内存中的数据被克隆了一份,数据会膨胀为原来的两倍,需要提前考虑

        最后一次数据可能会丢失

AOF(append only file)方式:

以日志方式记录每个写操作,不记录读操作,只能追加文件但不能改文件,换言之,redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作;

AOF默认不开启,如果AOF和RDB都开启了,那么默认会使用AOF

//修复aof文件
./redis-check-aof --fix ./appendonlydir/appendonly.aof.1.incr.aof

AOF持久化流程:

  • 客户端请求写命令被append追加到AOF缓冲区内;
  • AOF缓冲区根据AOF持久化策略【always,everysec,no】,将操作同步到磁盘的AOF文件中;
  • AOF文件大小超过重写策略或者手动重写时,会对AOF文件rewrite重写,压缩AOF文件容量;
  • reids服务重启时,会重新加载AOF文件的写操作达到数据恢复的目的;

 AOF的优势:

  • 备份机制更稳健,数据丢失率更低;
  • 可读的日志文本,通过操作AOF文件,可以修复误操作

AOF的劣势:

  • 比RDB更占用磁盘空间
  • 恢复速度更慢
  • 每次读写都同步,性能压力更大
  • 存在潜在BUG,造成无法恢复数据

总结:

  • RDB针对的是数据,AOF针对的是操作
  • 官方推荐两个都启用
  • 如果对数据不敏感,可以单独用RDB
  • 不推荐单独使用AOF,因此存在一些BUG
  • 如果只是做纯内存缓存,可以两个都不用

 17.主从复制

一个主服务器,多个从服务器,主服务负责写操作,从服务器负责读操作;

作用:

  • 读写分离,主服务器做写操作,再把数据复制到从服务器,读的时候直接访问读服务器,达到减小压力作用;
  • 容灾的快速恢复:从服务器一般存在多个,如果一个从服务器宕机,可以从其他从服务器读取;

操作(本演示操作是在同一个主机上启动3个redis模拟):

  • 在主服务器/目录下创建 myredis文件夹
  • 复制redis.conf到myredis/redis.conf
cp ./redis.conf /myredis/redis.conf
  • 配置一主两从 ,创建三个配置文件:redis6379.conf,redis6380.conf,redis6381.conf;
  • 编写配置文件:修改/myredis/redis.conf
appendonly no
  • vim redis6379.conf ,然后依次编写redis6380.conf,redis6381.conf
include /myredis/redis.conf

pidfile /var/run/redis_6379.pid

port 6379

dbfilename dump6379.rdb
  • 启动3个redis
//先配置环境变量
ln -s /usr/local/redis/bin/redis-server /usr/bin/redis-server
ln -s /usr/local/redis/bin/redis-cli /usr/bin/redis-cli

redis-server redis6379.conf
redis-server redis6380.conf
redis-server redis6381.conf

//通过端口连接各个redis,进入redis
redis-cli -p 6379

redis-cli -p 6380

redis-cli -p 6381

//查看主从情况
info replication
  • 在从机上执行
//slaveof 主机ip 主机端口号 

slaveof 127.0.0.1 6379

 注意点:

  • 当从服务宕机后,从服务器重新启动redis后,会变成独立的服务器,需要重新设置slaveof 127.0.0.1 6379,当设置完后,会自动同步主服务器的数据

过程:

  • 当从服务器连接上主服务器后,先向主服务器发送数据同步的消息;
  • 主服务器接收到从服务器的消息后,先将数据持久化到rdb文件,然后把rdb文件发送给从服务器,从服务器接收rdb文件并读取;
  • 后续每次主服务器进行写操作都会同步数据到从服务器;

反客为主:从服务器设置slaveof no one,即可由从服务器变成主服务器;

哨兵模式:反客为主需要手动设置,哨兵模式则可以实现自动的反客为主;其原理是:系统监控主机是否发生故障,如果存在故障,则自动投票将一个从库更改为主库;

投票的原则是:

  • 根据redis.conf中的replica-priority值(值越小优先级越高)
  • 如果priority值一致,则根据同步率匹配,选择同步率更高的从服务器作为主服务器
  • 如果上面两个都一致,则选择id较小的从服务器作为主服务器

操作:

  • 在/myredis目录下新建sentinel.conf文件编辑内如如下,名字固定
//mymaster:监控服务器名称 
//1:至少一个哨兵同意迁移主机,每一个从机都伴随着一个哨兵
sentinel monitor mymaster 127.0.0.1 6379 1
  • 启动哨兵
//设置环境变量
ln -s /usr/local/redis/bin/redis-sentinel /usr/bin/redis-sentinel

//启动
redis-sentinel sentinel.conf
  • 等待2分钟即可完成反客为主,如果主服务器再次运行,则变成从服务器,哨兵模式仍然存在一个缺陷,即从服务器转成主服务器时存在延时;

主从复制的缺陷:所有数据都是先写到主服务器,再同步到从服务器上,同步的过程存在延时问题,当业务量大的时候,延时问题更加严重,而且从机数量的增加也会使延时问题更加严重;

18.集群

redis集群实现了对redis的水平扩容,即启动N个redis节点,将整个数据库分布存储在N个节点中,某个节点存储总数据的1/N;即使集群中有一部分节点失效或者无法通讯,集群也可以继续处理命令请求;一个集群中至少要有3个主节点;

模拟搭建redis集群:

  • 新建redis6379.conf,复制5份redis配置文件
//编辑redis6379.conf
include /myredis/redis.conf

pidfile "/var/run/redis_6379.pid"

port 6379

dbfilename "dump6379.rdb"

#打开集群模式
cluster-enabled yes

#设定节点配置文件名
cluster-config-file nodes-6379.conf

#设定节点失联时间,超过该时间(毫秒),集群自动进行主从切换
cluster-node-timeout 15000


//然后复制,并修改相应端口号
redis6379.conf(主),redis6389.conf(从)
redis6380.conf(主),redis6390.conf(从)
redis6381.conf(主),redis6391.conf(从)
//修改后如下
include /myredis/redis.conf

pidfile "/var/run/redis_6391.pid"

port 6391

dbfilename "dump6391.rdb"

#打开集群模式
cluster-enabled yes

#设定节点配置文件名
cluster-config-file nodes-6391.conf

#设定节点失联时间,超过该时间(毫秒),集群自动进行主从切换
cluster-node-timeout 15000
  • 启动所有redis

  •  进入redis安装文件,并运行redis-cli指令
cd /usr/local/redis-7.0.2/src

//redis-cli --cluster create创建集群
//--cluster-replicas 1为集群中的每个主节点创建一个从节点
//此处不能用127.0.0.1,用真实ip地址
//本案例是在一台主机上模拟集群,实际上6台主机的ip都是不同的
redis-cli --cluster create --cluster-replicas 1 192.168.0.6:6379 192.168.0.6:6380 192.168.0.6:6381 192.168.0.6:6389 192.168.0.6:6390 192.168.0.6:6391
//尴尬的是,上面的操作碰到了6379端口connect refused,博主最后还是用127.0.0.1实现了;

//集群方式连接
redis-cli -c -p 6380    //6个当中的任意端口

//进入后,查看集群信息
cluster nodes
  • 完成后提示
//一个redis集群包含16384个插槽,数据库中的每个键都属于这16384个插槽中的一个,就是说该redis集群最多存储16384个key-value
//集群中的每个节点处理一部分插槽
//例如A节点处理0-5000,B节点处理5001-10001...
//实际创建key的时候,是通过一个算法,计算出key的插槽值,将该key-value分配到对应的节点来处理该key,如此可以达到各个节点平均分担插槽的效果,就是负载均衡;
All 16384 slots covered.

//此时同时新增多个key需要把key放到同一个组内,系统计算movie的插槽值,非配主机
 mset name{movie} txwz price{movie} 33.3 addr{movie} wanda

//计算key的插槽值
cluster keyslot k1  

//查看某个插槽值的键名,每个主机只能查看自己插槽范围的键
cluster countkeysinslot 4576

//返回某个插槽值中的多个键
cluster getkeysinslot 4576 2

集群创建节点的原则是:每个主节点尽量分配到不同IP地址中,每个从节点尽量跟主节点不在同一个IP;

集群恢复:

如果主节点宕机,从节点会自动转为主节点,之前的主节点恢复后,会变成新主节点的从节点;

如果某一段插槽的主从都挂掉,而cluster-require-full-coverage为yes,那么,整个集群都挂掉;

如果某一段插槽的主从都挂掉,而cluster-require-full-coverage为no,那么,该插槽数据全都不能使用,也无法存储;

在redis.conf中设置cluster-require-full-coverage;

redis集群优点:

  • 实现了扩容
  • 为多主机分摊压力
  • 无中心配置相对简单

redis集群缺点:

  • 多键操作不被支持
  • 多键事务不被支持,lua脚本不被支持
  • 由于集群方案出现的时间较晚,很多公司已经用其他方案实现了集群,迁移方式只能整体迁移没有逐步迁移,复杂度较大;

19.缓存穿透

一般redis是用来缓存数据的,当并发量大或者遭受攻击时,一个请求来redis查找一个不存在的数据,此时redis会向主数据库查找数据并缓存,这就导致redis不断的向主数据库查询数据,这样无疑加大了主数据库压力;

解决方案:

  • 对空结果缓存:如果一个查询返回是数据是空,仍然对空结果进行缓存,并设置很短的空结果过期时间,一般为5分钟;
  • 设置可访问的白名单:使用bitmaps类型定义一个可以访问的白名单,每次访问和bitmaps里面ip做比较,如果访问的ip不在bitmaps里面,进行拦截,不允许访问;
  • 布隆过滤器:原理与上面类似,但是效率高一些;
  • 实时监控:当发现redis缓存命中率急速降低时,可以和运维人员配合,排查访问对象和数据,设置黑名单限制服务;

但是一般遇到这个情况是遭遇黑客攻击了,第一时间报警最重要;

20.缓存击穿

当redis中并没有大量的key过期,只是某个key过期了,但此时某个热词出现,导致访问量突然加大,同时访问这个过期的key,此时对数据库的压力就会超载,导致缓存击穿;

解决方案:

  • 预先设置热门数据:在redis访问高峰之前,把一些热门数据提前存入到redis缓存里面,加大这些热门key的缓存时长;
  • 实时调整:现场监控某些key的热门程度,实时调整这些key的过期时间;
  • 使用锁:在缓存失效的时候(判断取出来的key是否是空),不是立即去load db;先对这个缓存设置排他锁,然后再查询数据库,如果别的线程已经在查询这个数据,则需先等待其他线程查询完毕,此方式可以完全解决缓存击穿问题,但是效率较低;

21.缓存雪崩

场景:极少时间段,查询大量的过期的key,造成瞬时服务器压力过大,造成服务器雪崩;

解决方案:

  • 构建多级缓存:nginx缓存+redis缓存+其他缓存等,对程序结构要求较为复杂;
  • 使用锁或队列:使用加锁或者队列的方式保证不会有大量的线程对数据库一次进行性读写,避免了缓存失效时,大量的并发请求落到了底层存储系统上,此方式不适用于高并发情况;
  • 设置过期标志更新缓存:记录缓存是否过期(设置提前量),如果过期调用其他线程到后台更新实际key缓存;
  • 将缓存失效时间分散开:在原有失效时间加上一个随机值,比如1到5分钟随机,这样每个缓存的过期时间的不会太集中,很难引发缓存集体失效事件;

22.分布式锁(共享锁)

使用分布式集群后,由于分布式系统多线程,多进程并且分布在不同的机器上,这将使原单机部署情况下的并发控制锁策略失效,单纯的java API并不能提供分布式锁的能力,为了解决这个问题,需要提供一种跨jvm的互斥机制来控制共享资源的问题,这就是分布式锁提供的能力;

分布式锁的主流实现方案:

  • 基于数据库实现分布式锁
  • 基于缓存(redis)
  • 基于zookeeper

其中,redis性能最高,zookeeper最可靠

redis方案:

//给key加锁,加锁后不能再修改该key,只有解锁了才能修改
setnx key value

//解锁
del key

//给锁设置过期时间,到期自动解锁
expire key second

//上锁之后如果服务器突然宕机,则无法给key设置过期时间
//上锁的时候同时设置过期时间即可,这就是操作的原子性
set movie 55 nx ex 15

代码实现:

@RequestMapping("/testLock")
    public String testLock(){
        //为该操作加集群锁
        Boolean absent = stringRedisTemplate.opsForValue().setIfAbsent("lock", "aaa",3, TimeUnit.SECONDS);
        //上锁成功
        if(absent){
            //业务操作...
            String num = stringRedisTemplate.opsForValue().get("num");
            if(StringUtils.isEmpty(num)){
                return "";
            }

            int i = Integer.parseInt(num);
            stringRedisTemplate.opsForValue().set("num",++i+"");
            stringRedisTemplate.delete("lock");
        }else{
            //上锁失败,则每隔0.5秒再操作一次
            try {
                Thread.sleep(500);
                testLock();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        return "";
    }

出现的问题:

a服务器上锁后,服务器卡顿,锁到期自动释放,但是操作还没执行完,此时b服务器抢到锁,执行操作,这个时候a服务器恢复操作,b服务器也在操作,然后a服务器执行到释放了锁操作,把b服务器的锁释放掉了,造成数据操作混乱;

 解决方案:

每台服务器设置uuid,防止其他服务器释放本服务器的锁

//设置uuid表示不同的操作
//set lock uuid nx ex 10

//释放锁的时候判断当前服务器uuid和释放的锁的uuid是否相同

@RequestMapping("/testLock")
    public String testLock(){
        String uuid = UUID.randomUUID().toString();

        //为该操作加集群锁,设置uuid
        Boolean absent = stringRedisTemplate.opsForValue().setIfAbsent("lock", uuid,3, TimeUnit.SECONDS);
        //上锁成功
        if(absent){
            //业务操作...
            String num = stringRedisTemplate.opsForValue().get("num");
            if(StringUtils.isEmpty(num)){
                return "";
            }

            int i = Integer.parseInt(num);
            stringRedisTemplate.opsForValue().set("num",++i+"");
            String lockUUID = stringRedisTemplate.opsForValue().get("lock");
            if(lockUUID.equals(uuid)){
                stringRedisTemplate.delete("lock");
            }
        }else{
            //上锁失败,则每隔0.5秒再操作一次
            try {
                Thread.sleep(500);
                testLock();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        return "";
    }

因为加锁没有原子性导致的问题:

 因为lua脚本有原子性,所以使用lua脚本执行:

@RequestMapping("/testLock")
    public String testLock() {
        String uuid = UUID.randomUUID().toString();

        // 为该操作加集群锁,设置uuid
        Boolean absent = stringRedisTemplate.opsForValue().setIfAbsent("lock", uuid, 3, TimeUnit.SECONDS);
        // 上锁成功
        if (absent) {
            // 业务操作...
            String num = stringRedisTemplate.opsForValue().get("num");
            if (StringUtils.isEmpty(num)) {
                return "";
            }

            int i = Integer.parseInt(num);
            stringRedisTemplate.opsForValue().set("num", ++i + "");
            String script = "if redis.call('get',KEYS[1]==ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end)";
            DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
            redisScript.setScriptText(script);
            redisScript.setResultType(Long.class);
            stringRedisTemplate.execute(redisScript, Arrays.asList("lock"), uuid);
        } else {
            // 上锁失败,则每隔0.5秒再操作一次
            try {
                Thread.sleep(500);
                testLock();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        return "";
    }

为了确保分布式锁的可用性,必须确保锁的实现同时满足以下4个条件:

  • 互斥性,在任意时刻,只有一个客户端能持有锁;
  • 不会发生死锁,即使有一个客户端在持有锁的期间发生崩溃而没有释放锁,也能保证后续其他客户端能加锁,即为每个锁设置过期时间,到时自动解锁;
  • 加锁和解锁必须是同一个客户端,加锁的客户端不能把别的客户端的锁解除;
  • 加锁和解锁必须具有原子性;

23.acl操作

//添加用户
acl setuser lucy

//查看用户
acl list

//查看当前操作用户
acl whoami

//设置用户密码,权限等操作 
// ~cached:*  只能使用获取 cached:开头的键
//+get 只能获取数据
acl setuser jack on >123456 ~cached:* +get

//切换用户
auth jack 123456

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

y_w_x_k

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值