Redis

NoSQL数据库

负载均衡场景

解决session问题?

方案:

  1. 存在客户端cookie;可能会存在安全问题
  2. 每个服务器都进行session复制;session一样,会浪费资源
  3. 使用NoSQL数据库,存在内存中,读写速度快

NoSQL数据库

NoSQL数据库,泛指非关系型数据库

以简单的key-value模式存储

  1. 不遵循SQL标准
  2. 不支持ACID
  3. 远超于SQL的性能

适用场景

  1. 对数据高并发的读写
  2. 海量数据的读写
  3. 对数据高可扩展性的

不适用的场景

  1. 需要事务支持

  2. 基于sql的结构化查询存储,处理复杂的关系,需要即席查

用不着sql的和用了sql也不行的情况,请考虑用NoSql

常用的NoSQL数据库

  1. Memcached
    很早出现的NoSQL数据库
    数据都在内存中,一般不持久化

    支持简单的key-value模式,支持类型单一

    一般是作为缓存数据库辅助持久化的数据库

  2. Redis

    几乎覆盖了Memcached的绝大部分功能

    数据都存在内存中,支持持久化,主要用做备份恢复

    除了支持简单的key-value模式,还支持多种数据结构的存储,比如list、set、hash、zset

    一般是作为缓存数据库辅助持久化的数据库

  3. MongoDB

    高性能、开源、模式自由的文档型数据库

    数据都存在内存中,如果内存不足,会把不常用的数据存到硬盘中

    虽然是key-value 模式,但是对value(尤其是json)提供了丰富的查询功能

    支持二进制数据及大型对象

    可以根据数据的特点代替RDBMS,成为独立的数据库,或者配合RDBMS,存储特定的数据

Redis

概述

REmote DIctionary Server(Redis) 是一个由 Salvatore Sanfilippo 写的 key-value 存储系统,是跨平台的非关系型数据库。

Redis 是一个开源的使用 ANSI C 语言编写、遵守 BSD 协议、支持网络、可基于内存、分布式、可选持久性的键值对(Key-Value)存储数据库,并提供多种语言的 API。

Redis 通常被称为数据结构服务器,因为值(value)可以是字符串(String)、哈希(Hash)、列表(list)、集合(sets)和有序集合(sorted sets)等类型。

  1. Redis是一个开源key-value存储系统
  2. 和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、 zset(sorted set --有序集合)和hash(哈希类型)
  3. 这些数据类型都支持push/pop、 add/remove 及取交集并集和差集及更丰富的操作,而且这些操作都是原子性
  4. 在此基础上, Redis支持各种不同方式的排序
  5. 与memcached一样,为了保证效率,数据都是缓存在内存
  6. 区别的是Redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件
  7. 并且在此基础上实现了master-slave(主从)同步

Redis

文档

单线程+多路IO复用

简单理解就是:一个服务端进程可以同时处理多个套接字描述符。
多路:多个客户端连接(连接就是套接字描述符)
复用:使用单进程就能够实现同时处理多个客户端的连接
以上是通过增加进程和线程的数量来并发处理多个套接字,免不了上下文切换的开销,而 IO 多路复用只需要一个进程就能够处理多个套接字,从而解决了上下文切换的问题。
其发展可以分 select->poll→epoll 三个阶段来描述。

五大数据类型

  1. 字符串(String)
  2. 列表(List)
  3. 集合(Set)
  4. 哈希(Hash)
  5. 有序集合(Zset)

命令操作

键key

keys * #查看数据库中所有的key

在这里插入图片描述

exists (key) #查看key是否存在,返回值为key的数量

在这里插入图片描述

type (key) #查看key是什么类型

在这里插入图片描述

del (key) #删除key,返回删除个数

在这里插入图片描述

unlink (key) #根据value选择非阻塞删除,先通知删除该key,后续在删除内存中的key,异步执行
expire (key) (time) #设置key的过期时间,单位秒

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5Jeahoax-1626772543889)(Redis.assets/image-20210608200044843.png)]

ttl (key) #查看key过期时间,-1表示永不过期,-2表示已经过期

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OpTRhiQi-1626772543890)(Redis.assets/image-20210608200218937.png)]

select (库序号) #切换库,redis有16个库,默认是0

在这里插入图片描述

dbsize #查看当前数据库key的数量,返回个数

在这里插入图片描述

flushdb #清除当前库
flushall #清除全部库

在这里插入图片描述

String(字符串)

String类型是(二进制安全的),可以包含任何数据

Srting类型是Redis最基本的数据类型,一个Redis中字符串的value最多可以是512M

set <key> <value> #设置一个key,value

get <key> #根据key获取value

在这里插入图片描述

append <key> <value> #在对应key的value后面追加数据,返回总长度

在这里插入图片描述

strlen <key> #获取值的长度
setnx <key> <value> #设置值,当键存在时不进行设置,键不存在才进行设置

在这里插入图片描述

incr <key> #将key中存储的值+1,返回增加后的值,只能对数字值进行操作,如果为空,新增值为1

在这里插入图片描述

decr <key> #将key中存储的值-1,返回减少后的值,只能对数字值进行操作,如果为空,新增值为-1

在这里插入图片描述

incrby/decrby <key> <步长> #将key中的值进行增减,长度为步长

在这里插入图片描述

增减操作都是原子性操作:Redis而言,命令的原子性指的是:一个操作的不可以再分,操作要么执行,要么不执行。

Redis的操作之所以是原子性的,是因为Redis是单线程的。

mset <key1> <value1> <key2> <value2> ... #同时对多对k-v进行赋值
mget <key1> <key2> <key3> ... #同时获取多个value

在这里插入图片描述

msetnx <key1> <value1> <key2> <value2> ... #同时设置多对值,当值存在时不进行设置,值不存在才进行设置

在这里插入图片描述

msetnx同时设置多对值时,原子性操作,要么都成功要么都不成功

#获取值的范围,类似Java中的substring,前后都为闭区间
getrange <key> <起始位置> <结束位置>

在这里插入图片描述

#根据起始位置,将key中的值覆盖为value
setrange <key> <起始位置> <value>

在这里插入图片描述

setex <key> <过期时间> <value> #设置k-v的同时设置过期时间,单位s

在这里插入图片描述

getset <key> <value> #设置新值的同时,获取旧值

在这里插入图片描述

String的底层结构为简单的动态字符串,内部结构类似与Java的ArrayList,才用预分配冗余空间的方式,来减少内存的频繁操作

在这里插入图片描述

内部为当前字符串实际分配的空间capacity一般要高于实际字符串长度len。当字符串长度小于1M时,扩容都是加倍现有的空间,如果超过1M,扩容时一次只会多扩1M的空间,需要注意的是字符串最大长度为512M

Hash(哈希)

hash是一个键值对集合

hash是一个string类型的field和value的映射表,hash特别适用于存储对象,类似Java的Map<string, object>

用户ID为查找的key,存储的value用户对象包含姓名,年龄,生日等信息,如果用普通的key/value结构来存储

主要有以下两种存储方式:

在这里插入图片描述

  1. 将对象系列化,存储序列化过后的对象,每次修改对象需要先反序列化,修改完数据后在序列化回去
  2. 普通键值对,键是用户id+属性标签,值是属性值,id数据冗余

第三种方式:

在这里插入图片描述

user为key,属性标签跟属性值是value,存储方便,值的操作方便

命令

hset <key> <filed> <value> [filed value..] #<key>是hash的键,<filed>是value的键,<value>是值,可以批量设置,如果hash的key不存在则创建新的hash,如果key存在则创建失败

在这里插入图片描述

hget <key> <filed> #根据filed获取value

在这里插入图片描述

hmset <key> <filed> <value> [filed value ..] #可以批量设置hash,如果key存在,filed相同则覆盖对应的value,否则创建一个新的hash

在这里插入图片描述

hmget <key> <filed> [filed ..] #批量获取value,如果filed不存在返回nil,存在返回对应的value

在这里插入图片描述

hexists <key> <filed> #判断对应的key是否存在filed

在这里插入图片描述

hkeys <key> #查询对应的key的所有filed

在这里插入图片描述

hvals <key> #查询对应key的所有value

在这里插入图片描述

hincrby <key> <field> <increment> #为hash的key中的field的值增加或减少increment

在这里插入图片描述

hsetnx <key> <field> <value> #为对应key添加filed和value,只有filed不存在时才会成功

在这里插入图片描述

Hash类型对应的数据结构有两种:ziplist(压缩列表),hashtable(哈希表)。当field-value长度较短且个数较少时,使用ziphash,否则使用hashtable

List(列表)

单键多值

redis列表是简单的字符串列表,按照插入顺序排序,可以插入一个元素到列表的头部(左边)或者尾部(右边)

列表类型内部使用双向链表实现的,所以向列表两端添加元素的时间复杂度为O(1),获取越接近两端的元素速度越快。但是使用链表的代价是通过索引访问元素比较慢。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-F4iM6YOi-1626772543909)(Redis.assets/image-20210608210412685.png)]

命令

命令用法描述
lpushlpush key value [value …](1)将一个或多个值插入到列表key的表头; (2)如果有多个value值,则从左到右的顺序依次插入表头; (3)如果key不存在,则会创建一个空列表,然后执行lpush操作;如果key存在,但不是列表类型,则返回错误。
lpushxlpushx key value(1)将value值插入到列表key的表头,当且仅当key存在且是一个列表; (2)如果key不存在时,lpushx命令什么都不会做。
lpoplpop key(1)移除并返回列表key的头元素。
lrangelrange key start stop(1)返回列表key中指定区间内的元素; (2)start大于列表最大下标是,返回空列表; (3)可使用负数下标,-1表示列表最后一个元素,以此类推。
lremlrem key count value(1)count>0表示从头到尾搜索,移除与value相等的元素,数量为count; (2)count<0表示从尾到头搜索,移除与value相等的元素,数量为count; (3)count=0表示移除列表中所有与value相等的元素。
lsetlset key index value(1)将列表key下标为index的元素值设置为value; (2)当index参数超出范围,或对一个空列表进行lset操作时,返回错误。
lindexlindex key index(1)返回列表key中下标为index的元素。
linsertlinsert key BEFORE|AFTER pivot value(1)将值value插入列表key中,位于pivot前面或者后面; (2)当pivot不存在列表key中,或者key不存在时,不执行任何操作。
llenlen key(1)返回列表key的长度,当key不存在时,返回0。
rpoprpop key(1)移除并返回列表key的尾元素。
rpoplpushrpoplpush source destination(1)将列表source中最后一个元素弹出并返回给客户端,并且将该元素插入到列表destincation的头部。
rpushrpush key value [value …](1)将一个或多个值插入到列表key的尾部。
rpushxrpushx key value(1)将value值插入到列表key的表尾,当且仅当key存在且是一个列表; (2)如果key不存在时,lpushx命令什么都不会做。

总结:

  1. 它是一个字符串链表,left 和 right 都可以插入、添加
  2. 如果键不存在,创建新的链表
  3. 如果键已存在,新增内容
  4. 如果值全移除,对应的键也就消失了
  5. 链表的操作无论是头和尾效率都极高,但假如是对中间元素进行操作,效率就很惨淡了

Set(集合)

Redis中的set类型是string类型的无序集合。集合类型的常用操作是向集合中加入或删除元素、判断某个元素是否存在等,由于集合类型在Redis内部是使用值为空的散列表实现的,所以这些操作的时间复杂度都是O(1)。最方便的是多个集合类型键之间还可以进行并集、交集和差集运算。

命令

命令用法描述
saddsadd key member [member …](1)将一个或多个member元素加入key中,已存在在集合中的member将被忽略; (2)如果key不存在,则创建一个只包含member元素的集合; (3)当key不是集合类型时,将返回一个错误。
scardscard key(1)返回key对应的集合中的元素数量。
sdiffsdiff key [key …](1)返回所有key对应的集合的差集。
sdiffstoresdiffstore destionation key [key …](1)返回所有key对应的集合的差集,并把该差集赋值给destionation; (2)如果destionation已经存在,则直接覆盖。
sintersinter key [key …](1)返回所有key对应的集合的交集; (2)不存在的key被视为空集。
sinterstoresinter destionation key [key …](1)返回所有key对应的集合的交集,并把该交集赋值给destionation; (2)如果destionation已经存在,则直接覆盖。
sismembersismember key member(1)判断member元素是否是key的成员,0表示不是,1表示是。
smemberssmember key(1)返回集合key中的所有成员; (2)不存在的key被视为空集。
sremsrem key member [member …](1)移除集合key中的一个或多个member元素,不存在的member将被忽略。
sunionsunion key [key …](1)返回所有key对应的集合的并集; (2)不存在的key被视为空集。
sunionstoresunionstore destionation key [key …](1)返回所有key对应的集合的并集,并把该并集赋值给destionation; (2)如果destionation已经存在,则直接覆盖。

Zset(sorted set有序集合)

zset类型也是string类型元素的集合,但是它是有序的。

命令用法描述
zaddzadd key score member [score member …](1)将一个或多个member元素及其score值加入集合key中; (2)如果member已经是有序集合的元素,那么更新member对应的score并重新插入member保证member在正确的位置上; (3)score可以是整数也可以是双精度浮点数。
zcardzcard key(1)返回有序集的元素个数。
zcountzcount key min max(1)返回有序集key中,score值>=min且<=max的成员数量
zrangezrange key start stop [withscores](1)返回有序集key中指定区间内的成员,成员位置按score从小到大排序; (2)如果score值相同,则按字典排序; (3)如果要使成员按score从大到小排序,则使用zrevrange命令。
zrankzrank key number(1)返回有序集key中成员member的排名,有序集合按score值从小到大排列; (2)zrevrank命令将按照score值从大到小排序。
zremzrem key member [member …](1)移除有序集key中的一个或多个元素,不存在的元素将被忽略; (2)当key存在但不是有序集时,返回错误。
zremrangebyrankzremrangerank key start stop(1)移除有序集key中指定排名区间内的所有元素。
zremrangebyscorezremrangescore key min max(1)移除有序集key中所有score值>=min且<=max之间的元素。

Spring Boot Redis

常用注解

@Cacheable

根据方法的请求参数对其结果进行缓存

  • key: 缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合(如:@Cacheable(value="user",key="#userName")
  • value: 缓存的名称,必须指定至少一个(如:@Cacheable(value="user") 或者 @Cacheable(value={"user1","use2"})
  • condition: 缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存(如:@Cacheable(value = "user", key = "#id",condition = "#id < 10")
@CachePut

根据方法的请求参数对其结果进行缓存,和 @Cacheable 不同的是,它每次都会触发真实方法的调用

  • key: 同上
  • value: 同上
  • condition: 同上
@CachEvict

根据条件对缓存进行清空

  • key: 同上
  • value: 同上
  • condition: 同上
  • allEntries: 是否清空所有缓存内容,缺省为 false,如果指定为 true,则方法调用后将立即清空所有缓存(如: @CacheEvict(value = "user", key = "#id", allEntries = true)
  • beforeInvocation: 是否在方法执行前就清空,缺省为 false,如果指定为 true,则在方法还没有执行的时候就清空缓存,缺省情况下,如果方法执行抛出异常,则不会清空缓存(如: @CacheEvict(value = "user", key = "#id", beforeInvocation = true)

依赖导入

<!--redis依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<!--redis客户端依赖,redis依赖自带lettuce-->
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
</dependency>

配置文件

spring:
	redis:
        database: 0
        #host: 192.168.190.134
        host: 127.0.0.1
        port: 6379
        #password: root
        timeout: 3000
        jedis:
          pool:
              max-active: 8
              max-wait: -1
              max-idle: 8
              min-idle: 0
    cache:
        type: redis

开启缓存

//在启动类上面开启缓存
@EnableCaching
@SpringBootApplication
public class QueryDslApplication {

    public static void main(String[] args) {
        SpringApplication.run(QueryDslApplication.class, args);
    }
}

使用缓存

@Override
//使用注解来使用缓存
@Cacheable(cacheNames = "emp", key = "#employee_id")
public EmployeeEntity findOne(Long employee_id) {
    EmployeeEntity entity = employeeRepository.findOne(employee_id).orElse(null);
    return entity;
}

三个注解的使用

package com.atguigu.cache.service;

import com.atguigu.cache.bean.Employee;
import com.atguigu.cache.mapper.EmployeeMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.*;
import org.springframework.stereotype.Service;

@CacheConfig(cacheNames="emp"/*,cacheManager = "employeeCacheManager"*/) //抽取缓存的公共配置
@Service
public class EmployeeService {

    @Autowired
    EmployeeMapper employeeMapper;

    /**
     * 将方法的运行结果进行缓存;以后再要相同的数据,直接从缓存中获取,不用调用方法;
     * CacheManager管理多个Cache组件的,对缓存的真正CRUD操作在Cache组件中,每一个缓存组件有自己唯一一个名字;
     *

     *
     * 原理:
     *   1、自动配置类;CacheAutoConfiguration
     *   2、缓存的配置类
     *   org.springframework.boot.autoconfigure.cache.GenericCacheConfiguration
     *   org.springframework.boot.autoconfigure.cache.JCacheCacheConfiguration
     *   org.springframework.boot.autoconfigure.cache.EhCacheCacheConfiguration
     *   org.springframework.boot.autoconfigure.cache.HazelcastCacheConfiguration
     *   org.springframework.boot.autoconfigure.cache.InfinispanCacheConfiguration
     *   org.springframework.boot.autoconfigure.cache.CouchbaseCacheConfiguration
     *   org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration
     *   org.springframework.boot.autoconfigure.cache.CaffeineCacheConfiguration
     *   org.springframework.boot.autoconfigure.cache.GuavaCacheConfiguration
     *   org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration【默认】
     *   org.springframework.boot.autoconfigure.cache.NoOpCacheConfiguration
     *   3、哪个配置类默认生效:SimpleCacheConfiguration;
     *
     *   4、给容器中注册了一个CacheManager:ConcurrentMapCacheManager
     *   5、可以获取和创建ConcurrentMapCache类型的缓存组件;他的作用将数据保存在ConcurrentMap中;
     *
     *   运行流程:
     *   @Cacheable:
     *   1、方法运行之前,先去查询Cache(缓存组件),按照cacheNames指定的名字获取;
     *      (CacheManager先获取相应的缓存),第一次获取缓存如果没有Cache组件会自动创建。
     *   2、去Cache中查找缓存的内容,使用一个key,默认就是方法的参数;
     *      key是按照某种策略生成的;默认是使用keyGenerator生成的,默认使用SimpleKeyGenerator生成key;
     *          SimpleKeyGenerator生成key的默认策略;
     *                  如果没有参数;key=new SimpleKey();
     *                  如果有一个参数:key=参数的值
     *                  如果有多个参数:key=new SimpleKey(params);
     *   3、没有查到缓存就调用目标方法;
     *   4、将目标方法返回的结果,放进缓存中
     *
     *   @Cacheable标注的方法执行之前先来检查缓存中有没有这个数据,默认按照参数的值作为key去查询缓存,
     *   如果没有就运行方法并将结果放入缓存;以后再来调用就可以直接使用缓存中的数据;
     *
     *   核心:
     *      1)、使用CacheManager【ConcurrentMapCacheManager】按照名字得到Cache【ConcurrentMapCache】组件
     *      2)、key使用keyGenerator生成的,默认是SimpleKeyGenerator
     *
     *
     *   几个属性:
     *      cacheNames/value:指定缓存组件的名字;将方法的返回结果放在哪个缓存中,是数组的方式,可以指定多个缓存;
     *
     *      key:缓存数据使用的key;可以用它来指定。默认是使用方法参数的值  1-方法的返回值
     *              编写SpEL; #i d;参数id的值   #a0  #p0  #root.args[0]
     *              getEmp[2]
     *
     *      keyGenerator:key的生成器;可以自己指定key的生成器的组件id
     *              key/keyGenerator:二选一使用;
     *
     *
     *      cacheManager:指定缓存管理器;或者cacheResolver指定获取解析器
     *
     *      condition:指定符合条件的情况下才缓存;
     *              ,condition = "#id>0"
     *          condition = "#a0>1":第一个参数的值》1的时候才进行缓存
     *
     *      unless:否定缓存;当unless指定的条件为true,方法的返回值就不会被缓存;可以获取到结果进行判断
     *              unless = "#result == null"
     *              unless = "#a0==2":如果第一个参数的值是2,结果不缓存;
     *      sync:是否使用异步模式
     * @param id
     * @return
     *
     */
    @Cacheable(value = {"emp"}/*,keyGenerator = "myKeyGenerator",condition = "#a0>1",unless = "#a0==2"*/)
    public Employee getEmp(Integer id){
        System.out.println("查询"+id+"号员工");
        Employee emp = employeeMapper.getEmpById(id);
        return emp;
    }

    /**
     * @CachePut:既调用方法,又更新缓存数据;同步更新缓存
     * 修改了数据库的某个数据,同时更新缓存;
     * 运行时机:
     *  1、先调用目标方法
     *  2、将目标方法的结果缓存起来
     *
     * 测试步骤:
     *  1、查询1号员工;查到的结果会放在缓存中;
     *          key:1  value:lastName:张三
     *  2、以后查询还是之前的结果
     *  3、更新1号员工;【lastName:zhangsan;gender:0】
     *          将方法的返回值也放进缓存了;
     *          key:传入的employee对象  值:返回的employee对象;
     *  4、查询1号员工?
     *      应该是更新后的员工;
     *          key = "#employee.id":使用传入的参数的员工id;
     *          key = "#result.id":使用返回后的id
     *             @Cacheable的key是不能用#result
     *      为什么是没更新前的?【1号员工没有在缓存中更新】
     *
     */
    @CachePut(/*value = "emp",*/key = "#result.id")
    public Employee updateEmp(Employee employee){
        System.out.println("updateEmp:"+employee);
        employeeMapper.updateEmp(employee);
        return employee;
    }

    /**
     * @CacheEvict:缓存清除
     *  key:指定要清除的数据
     *  allEntries = true:指定清除这个缓存中所有的数据
     *  beforeInvocation = false:缓存的清除是否在方法之前执行
     *      默认代表缓存清除操作是在方法执行之后执行;如果出现异常缓存就不会清除
     *
     *  beforeInvocation = true:
     *      代表清除缓存操作是在方法运行之前执行,无论方法是否出现异常,缓存都清除
     *
     *
     */
    @CacheEvict(value="emp",beforeInvocation = true/*key = "#id",*/)
    public void deleteEmp(Integer id){
        System.out.println("deleteEmp:"+id);
        //employeeMapper.deleteEmpById(id);
        int i = 10/0;
    }

    // @Caching 定义复杂的缓存规则
    @Caching(
         cacheable = {
             @Cacheable(/*value="emp",*/key = "#lastName")
         },
         put = {
             @CachePut(/*value="emp",*/key = "#result.id"),
             @CachePut(/*value="emp",*/key = "#result.email")
         }
    )
    public Employee getEmpByLastName(String lastName){
        return employeeMapper.getEmpByLastName(lastName);
    }
}

RedisAutoConfiguration

//操作对象,Cacheable默认实现的是redisTemplate,我们重写redisTemplate就可以实现自己的Cacheable
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
    RedisTemplate<Object, Object> template = new RedisTemplate<>();
    template.setConnectionFactory(redisConnectionFactory);
    return template;
}

@Bean
@ConditionalOnMissingBean
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
    StringRedisTemplate template = new StringRedisTemplate();
    template.setConnectionFactory(redisConnectionFactory);
    return template;
}

自定义KeyGenerator

Redis分布式锁

原生

使用redis中的setnx API,当key存在时不能设置value

  1. 配置环境

    server:
        port: 8080
    spring:
        redis:
            host: 192.168.190.137
            port: 6379
            database: 0
            timeout: 6000
            password: root
        cache:
            type: redis
    

    开启缓存

在这里插入图片描述

  1. controller层加锁,使用StringRedisTemplate

Redisson框架

Github文档

官网

Redisson是什么?

Redisson是Redis服务器上的分布式可伸缩Java数据结构----驻内存数据网格(In-Memory Data Grid,IMDG)。底层使用netty框架,并

提供了与java对象相对应的分布式对象、分布式集合、分布式锁和同步器、分布式服务等一系列的Redisson的分布式对象。

使用Redisson

  1. 导入依赖

    <!-- https://mvnrepository.com/artifact/org.redisson/redisson-spring-boot-starter -->
    <dependency>
        <groupId>org.redisson</groupId>
        <artifactId>redisson-spring-boot-starter</artifactId>
        <version>3.15.6</version>
    </dependency>
    
  2. 配置redisson.yml

    singleServerConfig:
        idleConnectionTimeout: 10000
        #pingTimeout: 1000
        connectTimeout: 10000
        timeout: 3000
        retryAttempts: 3
        retryInterval: 1500
        #reconnectionTimeout: 3000
        #failedAttempts: 3
        password: root
        subscriptionsPerConnection: 5
        clientName: null
        address: "redis://192.168.190.138:6379"
        subscriptionConnectionMinimumIdleSize: 1
        subscriptionConnectionPoolSize: 50
        connectionMinimumIdleSize: 32
        connectionPoolSize: 64
        database: 0
        #dnsMonitoring: false
        dnsMonitoringInterval: 5000
    threads: 0
    nettyThreads: 0
    codec: !<org.redisson.codec.JsonJacksonCodec> {}
    transportMode : "NIO"
    
  3. 添加配置类

    package com.du.config;
    
    import org.redisson.Redisson;
    import org.redisson.api.RedissonClient;
    import org.redisson.config.Config;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.core.io.ClassPathResource;
    
    import java.io.IOException;
    
    /**
     * @author Du
     */
    @Configuration
    public class RedissonConfig {
    
        @Bean(destroyMethod="shutdown")
        public RedissonClient redisson() throws IOException {
            //两种方式
            //1、导入YAML文件
            RedissonClient redisson = Redisson.create(
                    Config.fromYAML(new ClassPathResource("redisson-single.yml").getInputStream()));
            return redisson;
            
            //2、创建Config类,配置参数
            Config config = new Config();
            config.useSingleServer()
                    .setAddress("redis://192.168.190.140:6379")
                    .setDatabase(0)
                    .setPassword("root");
            config.setLockWatchdogTimeout(15000);
            return Redisson.create(config);
        }
    }
    
  4. 测试

    @Autowired
    RedissonClient redissonClient;
    
    @PostMapping("/redissonlock")
    public String redissonlock(@RequestParam String name) {
    
        // 加锁,添加key
        String lock = name + "-lock";
        RLock rLock = redissonClient.getLock(lock);
    
        try {
            rLock.lock();
            int apple = Integer.parseInt(Objects.requireNonNull(stringRedisTemplate.opsForValue().get(name)));
            if (apple > 0) {
                apple--;
                System.out.println("---------扣减成功," + name + "库存:" + apple);
                stringRedisTemplate.opsForValue().set("apple", String.valueOf(apple));
            } else {
                System.out.println("---------库存不足,扣减失败");
            }
        } finally {
            // 解锁,删除key
            rLock.unlock();
        }
        return "";
    }
    
watchdog

看门狗,redisson不指定leaseTime,默认创建一个30秒的看门狗。

在这里插入图片描述

在这里插入图片描述

核心源码

// 直接使用lock无参数方法
public void lock() {
    try {
        lock(-1, null, false);
    } catch (InterruptedException e) {
        throw new IllegalStateException();
    }
}

// 进入该方法 其中leaseTime = -1
private void lock(long leaseTime, TimeUnit unit, boolean interruptibly) throws InterruptedException {
    long threadId = Thread.currentThread().getId();
    Long ttl = tryAcquire(-1, leaseTime, unit, threadId);
    // lock acquired
    if (ttl == null) {
        return;
    }

   //...
}

// 进入 tryAcquire(-1, leaseTime, unit, threadId)
private Long tryAcquire(long waitTime, long leaseTime, TimeUnit unit, long threadId) {
    return get(tryAcquireAsync(waitTime, leaseTime, unit, threadId));
}

// 进入 tryAcquireAsync
private <T> RFuture<Long> tryAcquireAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId) {
    if (leaseTime != -1) {
        return tryLockInnerAsync(waitTime, leaseTime, unit, threadId, RedisCommands.EVAL_LONG);
    }
    //当leaseTime = -1 时 启动 watch dog机制
    RFuture<Long> ttlRemainingFuture = tryLockInnerAsync(waitTime,
                                            commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(),
                                            TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
    //执行完lua脚本后的回调
    ttlRemainingFuture.onComplete((ttlRemaining, e) -> {
        if (e != null) {
            return;
        }

        if (ttlRemaining == null) {
            // watch dog 
            scheduleExpirationRenewal(threadId);
        }
    });
    return ttlRemainingFuture;
}

scheduleExpirationRenewal 方法开启监控:

private void scheduleExpirationRenewal(long threadId) {
    ExpirationEntry entry = new ExpirationEntry();
    //将线程放入缓存中
    ExpirationEntry oldEntry = EXPIRATION_RENEWAL_MAP.putIfAbsent(getEntryName(), entry);
    //第二次获得锁后 不会进行延期操作
    if (oldEntry != null) {
        oldEntry.addThreadId(threadId);
    } else {
        entry.addThreadId(threadId);
        
        // 第一次获得锁 延期操作
        renewExpiration();
    }
}

// 进入 renewExpiration()
private void renewExpiration() {
    ExpirationEntry ee = EXPIRATION_RENEWAL_MAP.get(getEntryName());
    //如果缓存不存在,那不再锁续期
    if (ee == null) {
        return;
    }
    
    Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
        @Override
        public void run(Timeout timeout) throws Exception {
            ExpirationEntry ent = EXPIRATION_RENEWAL_MAP.get(getEntryName());
            if (ent == null) {
                return;
            }
            Long threadId = ent.getFirstThreadId();
            if (threadId == null) {
                return;
            }
            
            //执行lua 进行续期
            RFuture<Boolean> future = renewExpirationAsync(threadId);
            future.onComplete((res, e) -> {
                if (e != null) {
                    log.error("Can't update lock " + getName() + " expiration", e);
                    return;
                }
                
                if (res) {
                    //延期成功,继续循环操作
                    renewExpiration();
                }
            });
        }
        //每隔internalLockLeaseTime/3=10秒检查一次
    }, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);
    
    ee.setTimeout(task);
}

//lua脚本 执行包装好的lua脚本进行key续期
protected RFuture<Boolean> renewExpirationAsync(long threadId) {
    return evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
            "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
                    "redis.call('pexpire', KEYS[1], ARGV[1]); " +
                    "return 1; " +
                    "end; " +
                    "return 0;",
            Collections.singletonList(getName()),
            internalLockLeaseTime, getLockName(threadId));
}
结论:
  1. watch dog 在当前节点存活时每10s给分布式锁的key续期 30s;
  2. watch dog 机制启动,且代码中没有释放锁操作时,watch dog 会不断的给锁续期;
  3. 从可2得出,如果程序释放锁操作时因为异常没有被执行,那么锁无法被释放,所以释放锁操作一定要放到 finally {} 中;

看到3的时候,可能会有人有疑问,如果释放锁操作本身异常了,watch dog 不会继续续期

// 锁释放
public void unlock() {
    try {
        get(unlockAsync(Thread.currentThread().getId()));
    } catch (RedisException e) {
        if (e.getCause() instanceof IllegalMonitorStateException) {
            throw (IllegalMonitorStateException) e.getCause();
        } else {
            throw e;
        }
    }
}

// 进入 unlockAsync(Thread.currentThread().getId()) 方法 入参是当前线程的id
public RFuture<Void> unlockAsync(long threadId) {
    RPromise<Void> result = new RedissonPromise<Void>();
    //执行lua脚本 删除key
    RFuture<Boolean> future = unlockInnerAsync(threadId);

    future.onComplete((opStatus, e) -> {
        // 无论执行lua脚本是否成功 执行cancelExpirationRenewal(threadId) 方法来删除EXPIRATION_RENEWAL_MAP中的缓存
        cancelExpirationRenewal(threadId);

        if (e != null) {
            result.tryFailure(e);
            return;
        }

        if (opStatus == null) {
            IllegalMonitorStateException cause = new IllegalMonitorStateException("attempt to unlock lock, not locked by current thread by node id: "
                    + id + " thread-id: " + threadId);
            result.tryFailure(cause);
            return;
        }

        result.trySuccess(null);
    });

    return result;
}

// 此方法会停止 watch dog 机制
void cancelExpirationRenewal(Long threadId) {
    ExpirationEntry task = EXPIRATION_RENEWAL_MAP.get(getEntryName());
    if (task == null) {
        return;
    }
    
    if (threadId != null) {
        task.removeThreadId(threadId);
    }

    if (threadId == null || task.hasNoThreads()) {
        Timeout timeout = task.getTimeout();
        if (timeout != null) {
            timeout.cancel();
        }
        EXPIRATION_RENEWAL_MAP.remove(getEntryName());
    }
}

释放锁的操作中 有一步操作是从 EXPIRATION_RENEWAL_MAP 中获取 ExpirationEntry 对象,然后将其remove,结合watch dog中的续期前的判断:

EXPIRATION_RENEWAL_MAP.get(getEntryName());
if (ent == null) {
    return;
}

可以得出结论:

如果释放锁操作本身异常了,watch dog 还会不停的续期吗?不会,因为无论释放锁操作是否成功,EXPIRATION_RENEWAL_MAP中的目标 ExpirationEntry 对象已经被移除了,watch dog 通过判断后就不会继续给锁续期了。

分布式对象

每一个Redisson对象都有一个Redis数据实例相对应。

  1. 通用对象桶

    Redisson分布式对象RBucket,可以存放任意类型对象

  2. 二进制流

    Redisson分布式对象RBinaryStream,InputStream和OutoutStream接口实现

  3. 地理空间对象桶

    Reddisson分布式RGo,储存于地理位置有关的对象桶

  4. BitSet

    Reddisson分布式RBitSet,是分布式的可伸缩位向量通过实现RClusteredBitSet接口,可以在集群环境下数据分片

  5. 布隆过滤器

    Reddisson利用Redis实现了java分布式的布隆过滤器RBloomFilter

    public class BloomFilterExamples {
    
        public static void main(String[] args) {
            // connects to 127.0.0.1:6379 by default
            RedissonClient redisson = Redisson.create();
    
            RBloomFilter<String> bloomFilter = redisson.getBloomFilter("bloomFilter");
            bloomFilter.tryInit(100_000_000, 0.03);
            
            bloomFilter.add("a");
            bloomFilter.add("b");
            bloomFilter.add("c");
            bloomFilter.add("d");
            
            bloomFilter.getExpectedInsertions();
            bloomFilter.getFalseProbability();
            bloomFilter.getHashIterations();
            
            bloomFilter.contains("a");
            
            bloomFilter.count();
            
            redisson.shutdown();
        }
        
    }
    

    实现RClusteredBloomFilter接口,可以分片。通过压缩未使用的比特位来释放集群内存空间

  6. 基数估计算法(RHyperLogLog)

    可以在有限的空间通过概率算法统计大量数据

  7. 限流器(RRateLimiter )

    可以用来在分布式环境下限制请求方的调用频率。适用于不同或相同的Reddisson实例的多线程限流。并不保证公平性

    public class RateLimiterExamples {
    
        public static void main(String[] args) throws InterruptedException {
            // connects to 127.0.0.1:6379 by default
            RedissonClient redisson = Redisson.create();
    
            RRateLimiter limiter = redisson.getRateLimiter("myLimiter");
            // one permit per 2 seconds
            limiter.trySetRate(RateType.OVERALL, 1, 2, RateIntervalUnit.SECONDS);
            
            CountDownLatch latch = new CountDownLatch(2);
            limiter.acquire(1);
            latch.countDown();
    
            Thread t = new Thread(() -> {
                limiter.acquire(1);
                
                latch.countDown();
            });
            t.start();
            t.join();
            
            latch.await();
            
            redisson.shutdown();
        }
        
    }
    
  8. 及原子整长形(RAtomicLong )、原子双精度浮点(RAtomicDouble )、话题(订阅分发)(RTopic )、整长型累加器(RLongAdder )、双精度浮点累加器(RLongDouble )等分布式对象

分布式集合

在Redis分布式的集合元素与java对象相对应。包括:映射、多值映射(Multimap)、集(Set)、有序集(SortedSet)、计分排序集

(ScoredSortedSet)、字典排序集(LexSortedSet)、列表(List)、队列(Queue)、双端队列(Deque)、阻塞队列(Blocking Queue)

映射(RMap)

映射类型按照特性主要分为三类:元素淘汰、本地缓存、数据分片。

  1. 元素淘汰(Eviction)类:对映射中每个元素单独设置有效时间和最长闲置时间。保留了元素的插入顺序。不支持散列(hash)的元素

    淘汰,过期元素通过EvictionScheduler实例定期清理。实现类RMapCache

  2. 本地缓存(LocalCache)类:本地缓存也叫就近缓存,主要用在特定场景下。映射缓存上的高度频繁的读取操作,使网络通信被视

    为瓶颈的情况下。较分布式映射提高45倍。实现类RLocalCachedMap

  3. 数据分片(Sharding):利用分库的原理,将单一映射结构切分若干,并均匀分布在集群的各个槽里。可以突破Redis自身的容量限

    制,可以随集群的扩大而增长,也可以使读写性能和元素淘汰能力随之线性增长。主要实现类RClusteredMap

当然还有其他类型,比如映射监听器、LRU有界映射

映射监听器:监听元素的添加(EntryCreatedListener)、过期、删除、更新事件

LRU有界映射:根据时间排序,超过容量限制的元素会被删除

public class MapExamples {

    public static void main(String[] args) throws IOException {
        // connects to 127.0.0.1:6379 by default
        RedissonClient redisson = Redisson.create();
        
        RMap<String, Integer> map =  redisson.getMap("myMap");
        map.put("a", 1);
        map.put("b", 2);
        map.put("c", 3);
        
        boolean contains = map.containsKey("a");
        
        Integer value = map.get("c");
        Integer updatedValue = map.addAndGet("a", 32);
        
        Integer valueSize = map.valueSize("c");
        
        Set<String> keys = new HashSet<String>();
        keys.add("a");
        keys.add("b");
        keys.add("c");
        Map<String, Integer> mapSlice = map.getAll(keys);
        
        // use read* methods to fetch all objects
        Set<String> allKeys = map.readAllKeySet();
        Collection<Integer> allValues = map.readAllValues();
        Set<Entry<String, Integer>> allEntries = map.readAllEntrySet();
        
        // use fast* methods when previous value is not required
        boolean isNewKey = map.fastPut("a", 100);
        boolean isNewKeyPut = map.fastPutIfAbsent("d", 33);
        long removedAmount = map.fastRemove("b");
        
        redisson.shutdown();
    }
    
}
映射持久化方式(缓存策略)

将映射中的数据持久化到外部存储服务的功能

主要场景:

  1. 作为业务和外部存储媒介之间的缓存
  2. 用来增加数据的持久性、增加已被驱逐的数据的寿命
  3. 用来缓存数据库、web服务或其他数据源的数据

Read-through策略:如果在映射中不存在,则通过Maploader对象加载

Write-through策略(数据同步写入):对映射数据的更改则会通过MapWriter写入到外部存储系统,然后更新redis里面的数据

Write-behind策略(数据异步写入):对映射数据的更改先写到redis,然后使用异步方式写入到外部存储

分布式锁和同步器

如果负责存储分布式锁的Redisson节点宕机以后,而且这个锁正好处于锁住的状态,这时便会出现死锁。为了避免发生这种状况,提供了

一个看门狗,它的作用是在Redisson实例被关闭之前,不断延长锁的有效期。默认情况下,看门狗的检查锁的超时时间是30秒钟。

分布式锁种类有:可重入锁(Reentrant Lock)、公平锁、联锁(MultiLock)、红锁(RedLock)、 读写锁、信号量(Semaphore)、

可过期性信号量(PermitExpirableSemaphore)、闭锁(CountDownLatch)

public class LockExamples {

    public static void main(String[] args) throws InterruptedException {
        // connects to 127.0.0.1:6379 by default
        RedissonClient redisson = Redisson.create();
        
        RLock lock = redisson.getLock("lock");
        lock.lock(2, TimeUnit.SECONDS);

        Thread t = new Thread() {
            public void run() {
                RLock lock1 = redisson.getLock("lock");
                lock1.lock();
                lock1.unlock();
            };
        };

        t.start();
        t.join();

        lock.unlock();
        
        redisson.shutdown();
    }
    
}

红锁

public class RedLockExamples {

    public static void main(String[] args) throws InterruptedException {
        // connects to 127.0.0.1:6379 by default
        RedissonClient client1 = Redisson.create();
        RedissonClient client2 = Redisson.create();
        
        RLock lock1 = client1.getLock("lock1");
        RLock lock2 = client1.getLock("lock2");
        RLock lock3 = client2.getLock("lock3");
        
        Thread t1 = new Thread() {
            public void run() {
                lock3.lock();
            };
        };
        t1.start();
        t1.join();
        
        Thread t = new Thread() {
            public void run() {
                RedissonMultiLock lock = new RedissonRedLock(lock1, lock2, lock3);
                lock.lock();
                
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                }
                lock.unlock();
            };
        };
        t.start();
        t.join(1000);

        lock3.forceUnlock();
        
        RedissonMultiLock lock = new RedissonRedLock(lock1, lock2, lock3);
        lock.lock();
        lock.unlock();

        client1.shutdown();
        client2.shutdown();
    }
    
}

分布式服务

分布式服务包括分布式远程服务(RRemoteService )、分布式实时对象服务(RLiveObjectService )、分布式执行服务(RExecutorService )、分布式调度任务服务(RScheduledExecutorService )、分布式映射归纳服务(MapReduce)

  1. 分布式远程服务:实现了java的RPC远程调用,可以通过共享接口执行另一个Redisson实例里的对象方法。
  2. 分布式实时对象(RLO):使用生成的代理类,将一个指定的普通java类的所有字段以及这些字段的操作(get set方法)全部映射到一个Redis Hash的数据结构。get和set方法被转义为hget和hset命令,从而使所有连接到同一个redis节点的客户端同时对一个指定对象操作。通过将这些值保存在一个像redis这样的远程共享的空间的过程,把这个对象强化成一个分布式对象。这个对象就叫RLO。RLO使用方法:通过一系列注解@REntity(必选,类)、@RId(必选、主键字段)、@RIndex、@RObjectField、@RCascade(级联操作)
  3. 分布式执行服务:执行任务及取消任务
public class ExecutorServiceExamples {

    public static class RunnableTask implements Runnable, Serializable {

        @RInject
        RedissonClient redisson;

        @Override
        public void run() {
            RMap<String, String> map = redisson.getMap("myMap");
            map.put("5", "11");
        }
        
    }
    
    public static class CallableTask implements Callable<String>, Serializable {

        @RInject
        RedissonClient redisson;
        
        @Override
        public String call() throws Exception {
            RMap<String, String> map = redisson.getMap("myMap");
            map.put("1", "2");
            return map.get("3");
        }

    }
    
    public static void main(String[] args) {
        Config config = new Config();
        config.useClusterServers()
            .addNodeAddress("127.0.0.1:7001", "127.0.0.1:7002", "127.0.0.1:7003");
        
        RedissonClient redisson = Redisson.create(config);

        RedissonNodeConfig nodeConfig = new RedissonNodeConfig(config);
        nodeConfig.setExecutorServiceWorkers(Collections.singletonMap("myExecutor", 1));
        RedissonNode node = RedissonNode.create(nodeConfig);
        node.start();

        RExecutorService e = redisson.getExecutorService("myExecutor");
        e.execute(new RunnableTask());
        e.submit(new CallableTask());
        
        e.shutdown();
        node.shutdown();
    }
    
}

4、分布式调度任务服务:对计划任务的设定(可以通过CRON表达式)及去掉计划任务

public class SchedulerServiceExamples {

    public static class RunnableTask implements Runnable, Serializable {

        @RInject
        RedissonClient redisson;

        @Override
        public void run() {
            RMap<String, String> map = redisson.getMap("myMap");
            map.put("5", "11");
        }
        
    }
    
    public static class CallableTask implements Callable<String>, Serializable {

        @RInject
        RedissonClient redisson;
        
        @Override
        public String call() throws Exception {
            RMap<String, String> map = redisson.getMap("myMap");
            map.put("1", "2");
            return map.get("3");
        }

    }
    
    public static void main(String[] args) {
        Config config = new Config();
        config.useClusterServers()
            .addNodeAddress("127.0.0.1:7001", "127.0.0.1:7002", "127.0.0.1:7003");
        
        RedissonClient redisson = Redisson.create(config);

        RedissonNodeConfig nodeConfig = new RedissonNodeConfig(config);
        nodeConfig.setExecutorServiceWorkers(Collections.singletonMap("myExecutor", 5));
        RedissonNode node = RedissonNode.create(nodeConfig);
        node.start();

        RScheduledExecutorService e = redisson.getExecutorService("myExecutor");
        e.schedule(new RunnableTask(), 10, TimeUnit.SECONDS);
        e.schedule(new CallableTask(), 4, TimeUnit.MINUTES);

        e.schedule(new RunnableTask(), CronSchedule.of("10 0/5 * * * ?"));
        e.schedule(new RunnableTask(), CronSchedule.dailyAtHourAndMinute(10, 5));
        e.schedule(new RunnableTask(), CronSchedule.weeklyOnDayAndHourAndMinute(12, 4, Calendar.MONDAY, Calendar.FRIDAY));
        
        e.shutdown();
        node.shutdown();
    }
    
}

5、分布式映射归纳服务:通过映射归纳处理存储在Redis环境里的大量数据。示例代码单词统计

Redisson实现的锁

1. 可重入锁(Reentrant Lock)

Redisson的分布式可重入锁RLock Java对象实现了java.util.concurrent.locks.Lock接口,同时还支持自动过期解锁。

public void testReentrantLock(RedissonClient redisson){

        RLock lock = redisson.getLock("anyLock");
        try{
            // 1. 最常见的使用方法
            //lock.lock();

            // 2. 支持过期解锁功能,10秒钟以后自动解锁, 无需调用unlock方法手动解锁
            //lock.lock(10, TimeUnit.SECONDS);

            // 3. 尝试加锁,最多等待3秒,上锁以后10秒自动解锁
            boolean res = lock.tryLock(3, 10, TimeUnit.SECONDS);
            if(res){    //成功
                // do your business

            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    }

Redisson同时还为分布式锁提供了异步执行的相关方法:

public void testAsyncReentrantLock(RedissonClient redisson){
        RLock lock = redisson.getLock("anyLock");
        try{
            lock.lockAsync();
            lock.lockAsync(10, TimeUnit.SECONDS);
            Future<Boolean> res = lock.tryLockAsync(3, 10, TimeUnit.SECONDS);

            if(res.get()){
                // do your business

            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    }
2. 公平锁(Fair Lock)

Redisson分布式可重入公平锁也是实现了java.util.concurrent.locks.Lock接口的一种RLock对象。在提供了自动过期解锁功能的同时,保证了当多个Redisson客户端线程同时请求加锁时,优先分配给先发出请求的线程。

public void testFairLock(RedissonClient redisson){

    RLock fairLock = redisson.getFairLock("anyLock");
    try{
        // 最常见的使用方法
        fairLock.lock();

        // 支持过期解锁功能, 10秒钟以后自动解锁,无需调用unlock方法手动解锁
        fairLock.lock(10, TimeUnit.SECONDS);

        // 尝试加锁,最多等待100秒,上锁以后10秒自动解锁
        boolean res = fairLock.tryLock(100, 10, TimeUnit.SECONDS);
    } catch (InterruptedException e) {
        e.printStackTrace();
    } finally {
        fairLock.unlock();
    }

}

Redisson同时还为分布式可重入公平锁提供了异步执行的相关方法:

RLock fairLock = redisson.getFairLock("anyLock");
fairLock.lockAsync();
fairLock.lockAsync(10, TimeUnit.SECONDS);
Future<Boolean> res = fairLock.tryLockAsync(100, 10, TimeUnit.SECONDS);
3. 联锁(MultiLock)

Redisson的RedissonMultiLock对象可以将多个RLock对象关联为一个联锁,每个RLock对象实例可以来自于不同的Redisson实例。

public void testMultiLock(RedissonClient redisson1,
                              RedissonClient redisson2, RedissonClient redisson3){

        RLock lock1 = redisson1.getLock("lock1");
        RLock lock2 = redisson2.getLock("lock2");
        RLock lock3 = redisson3.getLock("lock3");

        RedissonMultiLock lock = new RedissonMultiLock(lock1, lock2, lock3);

        try {
            // 同时加锁:lock1 lock2 lock3, 所有的锁都上锁成功才算成功。
            lock.lock();

            // 尝试加锁,最多等待100秒,上锁以后10秒自动解锁
            boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS);

        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    }
4. 红锁(RedLock)

Redisson的RedissonRedLock对象实现了Redlock介绍的加锁算法。该对象也可以用来将多个RLock
对象关联为一个红锁,每个RLock对象实例可以来自于不同的Redisson实例。

    public void testRedLock(RedissonClient redisson1,
                              RedissonClient redisson2, RedissonClient redisson3){

        RLock lock1 = redisson1.getLock("lock1");
        RLock lock2 = redisson2.getLock("lock2");
        RLock lock3 = redisson3.getLock("lock3");

        RedissonRedLock lock = new RedissonRedLock(lock1, lock2, lock3);
      try {
            // 同时加锁:lock1 lock2 lock3, 红锁在大部分节点上加锁成功就算成功。
            lock.lock();

            // 尝试加锁,最多等待100秒,上锁以后10秒自动解锁
            boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS);

        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    }
5. 读写锁(ReadWriteLock)

Redisson的分布式可重入读写锁RReadWriteLock Java对象实现了java.util.concurrent.locks.ReadWriteLock接口。同时还支持自动过期解锁。该对象允许同时有多个读取锁,但是最多只能有一个写入锁。

RReadWriteLock rwlock = redisson.getLock("anyRWLock");
// 最常见的使用方法
rwlock.readLock().lock();
// 或
rwlock.writeLock().lock();

// 支持过期解锁功能
// 10秒钟以后自动解锁
// 无需调用unlock方法手动解锁
rwlock.readLock().lock(10, TimeUnit.SECONDS);
// 或
rwlock.writeLock().lock(10, TimeUnit.SECONDS);

// 尝试加锁,最多等待100秒,上锁以后10秒自动解锁
boolean res = rwlock.readLock().tryLock(100, 10, TimeUnit.SECONDS);
// 或
boolean res = rwlock.writeLock().tryLock(100, 10, TimeUnit.SECONDS);
...
lock.unlock();
6. 信号量(Semaphore)

Redisson的分布式信号量(Semaphore)Java对象RSemaphore采用了与java.util.concurrent.Semaphore相似的接口和用法。

RSemaphore semaphore = redisson.getSemaphore("semaphore");
semaphore.acquire();
//或
semaphore.acquireAsync();
semaphore.acquire(23);
semaphore.tryAcquire();
//或
semaphore.tryAcquireAsync();
semaphore.tryAcquire(23, TimeUnit.SECONDS);
//或
semaphore.tryAcquireAsync(23, TimeUnit.SECONDS);
semaphore.release(10);
semaphore.release();
//或
semaphore.releaseAsync();
7. 可过期性信号量(PermitExpirableSemaphore)

Redisson的可过期性信号量(PermitExpirableSemaphore)实在RSemaphore对象的基础上,为每个信号增加了一个过期时间。每个信号可以通过独立的ID来辨识,释放时只能通过提交这个ID才能释放。

RPermitExpirableSemaphore semaphore = redisson.getPermitExpirableSemaphore("mySemaphore");
String permitId = semaphore.acquire();
// 获取一个信号,有效期只有2秒钟。
String permitId = semaphore.acquire(2, TimeUnit.SECONDS);
// ...
semaphore.release(permitId);
8. 闭锁(CountDownLatch)

Redisson的分布式闭锁(CountDownLatch)Java对象RCountDownLatch采用了与java.util.concurrent.CountDownLatch相似的接口和用法。

RCountDownLatch latch = redisson.getCountDownLatch("anyCountDownLatch");
latch.trySetCount(1);
latch.await();

// 在其他线程或其他JVM里
RCountDownLatch latch = redisson.getCountDownLatch("anyCountDownLatch");
latch.countDown();
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

__mozzie__

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

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

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

打赏作者

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

抵扣说明:

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

余额充值