noSql--redis笔记

一、基础杂项知识

 

1.默认16个数据库,类似数组下标从0开始,默认使用零号库,默认端口6379
2.select命令切换数据库
3.dbsize查看当前数据库key的数量
4.keys列出所有key(使用keys k?,可以只列出k开头的)
5.flushdb清除当前库的key
6.flushall清除所有库的key

二、基本数据类型

1.key操作

 keys *:列出所有key
 set/get/del key :设置、查询、删除key
 exists key:判断某个key是否存在,1存在,0不存在
 move key dbnum:移到目标库,当前库就没有了
 expire key time(秒钟):为给定的key设置过期时间
 ttl key :查看还有多少秒过期,-1表示永不过期,-2表示已过期
 type key :查看你的key是什么类型

2.String

 append key value1:在key后追加value
 strlen key:key存储的value长度
 incr key:自增加1,value要是数字
 incrby key num:自增指定步长
 decr key:自减1
 decrby key num:自减指定步长
 setrange key 0 xx:设置指定区间范围内的值,下标0后2个字符为x
 getrange key 0 3:获得指定区间范围内的值,stop下标为-1表示全部
 setex key time value:设置value并设置其过期时间,如果key已存在就覆盖
 setnx key:设置value,只要key不存在生效,不会覆盖
 mset k1 v2 k2 v2:同时设置一个或多个 key-value 对
 mget k1 k2::获取所有(一个或多个)给定 key 的值
 msetnx k1 v2 k2 v2::同时设置一个或多个 key-value 对,当且仅当所有给定 key 都不存在。

3.List

 lpush lkey v1 v2 v3:设置值,存入顺序是倒序
 rpush lkey v1 v2 v3:设置值,存入顺序是正序
 lrange lkey 0 -1:获得指定区间范围内的值,stop下标为-1表示全部
 lpop lkey:正序开始取出第一个,取出后list里就没有了
 rpop lkey:倒序开始取出第一个
 lindex lkey i:通过索引获取列表中的元素
 llen lkey:获得list大小
 lrem lkey num  value:删除num个value
 ltrim lkey start end:截取指定索引区间的元素,再赋值给key
 rpoplpush lkey1 lkey2:把lkey1底部一个元素移到lkey2首部
 lset lkey index value:设置下标index的值
 linsert lkey before/after v1 v2:在v1 之前或之后插入v2

 4.set(无序集合,自动去重)

 sadd skey v1 v2:添加set数据
 smembers skey:查看数据
 sismember skey v1:集合是否存在v1元素
 scard skey:获得集合元素个数
 srem skey v1:删除值
 srandmember skey count:随机返回count个元素
 spop skey:随机取出一个元素,取出的元素在set里就没有了
 smove skey1 skey2 v:将skey1中的值v移到skey2
 sdiff skey1 skey2 :以skey2为参照,列出skey1不在skey2的元素(差集)
 sinter skey1 skey2:列出同时在skey1和skey2的元素(交集)
 sunion skey1 skey2:列出skey1和skey2的并集

5.hash(类似 map)

 hset hkey field value:根据key、field设置值
 hget hkey field:获得值
 hmset hkey f1 v1 f2 v2:设置多个值
 hmget hkey f1 f2:获得多个值
 hgetall hkey:获得所有值,返回形式是field-value
 hdel hkey field:删除元素
 hlen hkey:获得个数
 hexists hkey field:查看field是否存在
 hkeys/hvals hkey:获得所有field和value值
 hincrby/hincrbyfloat hkey field:对field的自增步长
 hsetnx hkey field value:设置file值,但必须field不存在

6.Zset(sorted set)(有序,去重)

 (在set基础上,加一个score值。之前set是k1 v1 v2 v3,现在zset是k1 score1 v1 score2 v2)

 zadd  zkey score1 value1 score2 value2:添加数据
 zrange/zrevrange zkey start stop 【withscores】:顺序列出所有value,如果加上withscores会返回设置的score,zrevrange是倒序列出
 zrangebyscore/zrevrangebyscore  zkey 【(】min max【withscores】【limit offset count】:顺序列出score在min和max之间的values(默认是min<=score<=max,如果另外设置了符号“(”是不包含临界条件),如果设置了limit就只返回从下标位移offset的count个元素;zrevrangebyscore是倒序列出,后面是max min区间
 zrem zkey value:删除vaule
 zcard zkey:获得集合个数
 zcount zkey min max:获得min和max之间的个数(包括条件值)
 zscore zkey value:获得value对应的score值
 zrank/zrevrank zkey value:获得value对应的下标,zrevrank是倒序的下标

注:命令详解http://www.runoob.com/redis/redis-lists.html  http://www.redis.cn/

 三、解析redis.conf配置

1.units单元:配置大小单位,开头定义了一些基本的度量单位,只支持bytes,不支持bit; 对大小写不敏感
============GENERAL通用配置================
2.daemonize:默认no,设置yes,redis作为守护进程后台运行,它会写一个 pid 到 /var/run/redis.pid 文件里面
3.pidfile/port:设置pidfile文件路径和服务端口(默认6379)
4.tcp-backlog:设置tcp的backlog,backlog其实是一个连接队列,backlog队列总和=未完成三次握手队列 + 已经完成三次握手队列。在高并发环境下你需要一个高backlog值来避免慢客户端连接问题。注意Linux内核会将这个值减小到/proc/sys/net/core/somaxconn的值,所以需要确认增大somaxconn和tcp_max_syn_backlog两个值来达到想要的效果(默认511)
5.timeout:客户端闲置多少秒后,断开连接(默认0禁用此功能)
6.tcp-keepalive:指定TCP连接是否为长连接,”侦探”信号由server端维护,长连接将会额外的增加server端的开支。单位为秒,默认为0.表示禁用,非0值表示开启”长连接” 间隔时间发送”侦探”信号,在多次”侦探”后,如果对等端仍不回复,将会关闭连接,否则连接将会被保持开启.建议设置成60
7.loglevel :日志级别,server日志级别,合法值:debug,verbose,notice,warning 默认为notice
8.logfile:日志文件名
9.databases :设置数据库的数量,默认16个,默认连接的数据库是0,可以通过select N来连接不同的数据库
=============SECURITY安全设置===============
10.SECURITY安全设置:config get requirepass/config set requirepass pwd
=============LIMITS限制======================
11.maxclients:设置redis同时可以与多少个客户端进行连接。默认情况下为10000个客户端
12.maxmemory:设置redis可以使用的内存量。一旦到达内存使用上限,redis将会试图移除内部数据,移除规则可以通过maxmemory-policy来指定。如果redis无法根据移除规则来移除内存中的数据,或者设置了“不允许移除”,
那么redis则会针对那些需要申请内存的指令返回错误信息,比如SET、LPUSH等。但是对于无内存申请的指令,仍然会正常响应,比如GET等
13.maxmemory-policy
(1)volatile-lru:使用LRU(最近最少使用)算法移除key,只对设置了过期时间的键
(2)allkeys-lru:使用LRU算法移除key
(3)volatile-random:在过期集合中移除随机的key,只对设置了过期时间的键
(4)allkeys-random:移除随机的key
(5)volatile-ttl:移除那些TTL值最小的key,即那些最近要过期的key
(6)noeviction:不进行移除。针对写操作,只是返回错误信息
14.maxmemory-samples:设置样本数量(默认5个),LRU算法和最小TTL算法都并非是精确的算法,而是估算值,所以你可以设置样本的大小,redis默认会检查这么多个key并选择其中LRU的那个
===========SNAPSHOTTING快照================
15. save 秒钟 写操作次数:是触发RDB快照条件,默认是1分钟内改了1万次,或5分钟内改了10次,或15分钟内改了1次。如果想禁用RDB持久化的策略,只要不设置任何save指令,或者给save传入一个空字符串参数也可以
16.stop-writes-on-bgsave-error:是否在后台save异常时停止写操作,默认yes
17.rdbcompression:对于存储到磁盘中的快照,可以设置是否进行压缩存储,默认yes。如果是的话,redis会采用
LZF算法进行压缩。如果你不想消耗CPU来进行压缩的话,可以设置为关闭此功能
18.rdbchecksum:在存储快照后,还可以让redis使用CRC64算法来进行数据校验,默认yes。但是这样做会增加性能消耗,如果希望获取到最大的性能提升,可以关闭此功能
19.dbfilename:RDB持久化文件名,默认dump.rdb
==========APPEND ONLY MODE追加===========
20.appendonly:是否开启AOF,yes开启,默认no
21.appendfilename:AOF持久化文件名,默认appendonly.aof
22.appendfsync:持久化触发点,默认everysec
always:同步持久化 每次发生数据变更会被立即记录到磁盘  性能较差但数据完整性比较好;
everysec:出厂默认推荐,异步操作,每秒记录   如果一秒内宕机,有数据丢失
no:从不
23.no-appendfsync-on-rewrite:重写时是否可以运用Appendfsync,用默认no即可,保证数据安全性。
24.auto-aof-rewrite-min-size:设置重写的文件大小基准值,默认64m
25.auto-aof-rewrite-percentage:设置重写文件比例基准值,默认100%,即为原来文件的2倍

四、redis持久化

1、RDB(Redis DataBase)

(1)说明:在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是Snapshot快照,它恢复时是将快照文件直接读到内存里。Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何IO操作的,这就确保了极高的性能。如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。RDB的缺点是最后一次持久化后的数据可能丢失。

(2)Fork:fork的作用是复制一个与当前进程一样的进程。新进程的所有数据(变量、环境变量、程序计数器等)数值都和原进程一致,但是是一个全新的进程,并作为原进程的子进程

(3)触发条件
  配置文件的快照save配置
  命令save或者是bgsave(save命令只管保存,其它不管,全部阻塞;bgsave命令Redis会在后台异步进行快照操作,快照同时还可以响应客户端请求)
  执行flushall命令,也会产生dump.rdb文件,但里面是空的,无意义

(4)修复文件:redis-check-dump --fix 错误rdb文件

(5)如何恢复:将备份文件 (dump.rdb) 移动到 redis 安装目录并启动服务即可(CONFIG GET dir获取目录)

(6)总结:RDB适合大规模的数据恢复、对数据完整性和一致性要求不高;但它是在一定间隔时间做一次备份,所以如果redis意外down掉的话,就会丢失最后一次快照后的所有修改,而且fork的时候,内存中的数据被克隆了一份,大致2倍的膨胀性需要考虑

2、AOF(Append Only File)

(1)说明:AOF以日志的形式来记录每个写操作,将Redis执行过的所有写指令记录下来(读操作不记录),只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作

(2)AOF启动/修复/恢复:启动修改appendonly为yes;修复使用redis-check-aof --fix 错误aof文件;恢复将有数据的aof文件复制一份保存到对应目录(config get dir);

(3)rewrite重写机制:
AOF采用文件追加方式,文件会越来越大为避免出现此种情况,新增了重写机制,当AOF文件的大小超过所设定的阈值时,Redis就会启动AOF文件的内容压缩,只保留可以恢复数据的最小指令集.可以使用命令bgrewriteaof
重写原理: AOF文件持续增长而过大时,会fork出一条新进程来将文件重写(也是先写临时文件最后再rename),
遍历新进程的内存中数据,每条记录有一条的Set语句。重写aof文件的操作,并没有读取旧的aof文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的aof文件,这点和快照有点类似
触发机制:Redis会记录上次重写时的AOF大小,默认配置是当AOF文件大小是上次rewrite后大小的一倍且文件大于64M时触发

(4)总结:可以比较完整的备份数据,追加文件会越来越大,有重写机制改善相同数据集的数据而言aof文件要远大于rdb文件,恢复速度慢于rdb,aof运行效率要慢于rdb

3、持久化总结

(1)RDB持久化方式能够在指定的时间间隔能对你的数据进行快照存储,恢复较快;AOF持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF命令以redis协议追加保存每次写的操作到文件末尾.Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大。

(2)相同数据集的数据而言aof文件要远大于rdb文件,恢复速度慢于rdb,aof运行效率要慢于rdb

(3)redis支持同时开启两种持久化方式,在这种情况下,当redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整.

五、事务

1、说明:可以一次执行多个命令,本质是一组命令的集合。一个事务中的所有命令都会序列化,按顺序地串行化执行而不会被其它命令插入,不许加塞。

2、使用事务过程:首先用MULTI命令开启一个事务;然后将多个需要执行命令入队到事务队列中(这些命令并不会立即执行);最后由EXEC命令触发事务,执行队列命令

3、放弃事务:在使用multi开启事务后,可以使用discard命令取消

4、watch监控:监控了key,如果在这个过程中key被修改了,那后面一个事务的执行失效。使用过程是watch key1 key2,然后使用事务。(另外可以使用unwatch取消监控)
Watch指令,类似乐观锁,事务提交时,如果Key的值已被别的客户端改变,比如某个list已被别的客户端push/pop过了,整个事务队列都不会被执行
通过WATCH命令在事务执行之前监控了多个Keys,倘若在WATCH之后有任何Key的值发生了变化,EXEC命令执行的事务都将被放弃,同时返回Nullmulti-bulk应答以通知调用者事务执行失败

5、总结:
单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
不保证原子性:redis同一个事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚;但如果有条命令是类似语法错误,加入事务队列时会立即报错,其他命令也不会执行

六、Master/Slave主从复制

1、说明:主从复制,主机数据更新后根据配置和策略,自动同步到备机的master/slaver机制,Master以写为主,Slave以读为主。作用在于读写分离、容灾恢复。

2、从库配置:实现主从复制,一般是配置从库。使用命令:slaveof 主库IP 主库端口,每次与master断开之后,都需要重新连接,除非你配置进redis.conf文件。使用info replication命令可以查看主从信息。

3、链式从库:上一个Slave可以是下一个slave的Master,Slave同样可以接收其他slaves的连接和同步请求,那么该slave作为了链条中下一个的master,可以有效减轻master的写压力。

4、停止从库复制:SLAVEOF no one   使当前从库停止与其master的同步复制,转成主库

5、复制原理:slave启动成功连接到master后会发送一个sync命令,Master接到命令启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令,在后台进程执行完毕之后,master将传送整个数据文件到slave,以完成一次完全同步。在主从库工作过程中首次连接master是全量复制(从库断开重连也是),后续Master将新的所有收集到的修改命令依次传给slave,完成增量复制。

6、哨兵模式(sentinel):自动能够后台监控主库是否故障,如果故障了根据投票数自动将从库转换为主库。另外如果之前故障的master重启回来,它会变成从库,同步复制投票选出的主库。配置步骤如下:
步骤1:在redis目录下新建sentinel.conf文件
步骤2:配置哨兵,填写内容(可以添加多个监控)
sentinel monitor 被监控主库名字(自己起名字) 127.0.0.1 6379 1
(上面最后一个数字1,表示主机挂掉后salve投票看让谁接替成为主机,得票数多少后成为主机)
步骤3:启动哨兵
redis-sentinel  redis目录/sentinel.conf
(Windows启动:redis-server.exe sentinel.conf --sentinel)

7、主从复制缺点:由于所有的写操作都是先在Master上操作,然后同步更新到Slave上,所以从Master同步到Slave机器有一定的延迟,当系统很繁忙的时候,延迟问题会更加严重,Slave机器数量的增加也会使这个问题更加严重。

补充:

1、复制流程:

2、全量复制

  • master 执行 bgsave ,在本地生成一份 rdb 快照文件。
  • master node 将 rdb 快照文件发送给 slave node,如果 rdb 复制时间超过 60秒(repl-timeout),那么 slave node 就会认为复制失败,可以适当调大这个参数(对于千兆网卡的机器,一般每秒传输 100MB,6G 文件,很可能超过 60s)
  • master node 在生成 rdb 时,会将所有新的写命令缓存在内存中,在 slave node 保存了 rdb 之后,再将新的写命令复制给 slave node。
  • 如果在复制期间,内存缓冲区持续消耗超过 64MB,或者一次性超过 256MB,那么停止复制,复制失败。
client-output-buffer-limit slave 256MB 64MB 60
  • slave node 接收到 rdb 之后,清空自己的旧数据,然后重新加载 rdb 到自己的内存中,同时基于旧的数据版本对外提供服务。
  • 如果 slave node 开启了 AOF,那么会立即执行 BGREWRITEAOF,重写 AOF
  • 支持无磁盘复制:master 在内存中直接创建 RDB,然后发送给 slave,不会在自己本地落地磁盘了。只需要在配置文件中开启 repl-diskless-sync yes 即可

3、增量复制

从Redis 2.8开始,如果遭遇连接断开,重新连接之后可以从中断处继续进行复制,而不必重新同步。

主服务器端为复制流维护一个内存缓冲区(in-memory backlog)。主从服务器都维护一个复制偏移量(replication offset)和master run id ,当连接断开时,从服务器会重新连接上主服务器,然后请求继续复制,假如主从服务器的两个master run id相同,并且指定的偏移量在内存缓冲区backlog中还有效,复制就会从上次中断的点开始继续。如果其中一个条件不满足,就会进行完全重新同步。

Slave初始化后开始正常工作后,主服务器每执行一个写命令就会向从服务器发送相同的写命令,从服务器接收并执行收到的写命令。

4、异步复制

主从复制对于主redis服务器来说是非阻塞的,这意味着当从服务器在进行主从复制同步过程中,主redis仍然可以处理外界的访问请求。

主从复制对于从redis服务器来说也是非阻塞的,这意味着,即使从redis在进行主从复制过程中也可以接受外界的查询请求,只不过这时候从redis返回的是以前老的数据。(也可以在配置文件中进行设置,那么从redis在复制同步过程中来自外界的查询请求都会返回错误给客户端)

七、java客户端Jedis

1、连通测试(引用jedis-2.1.0.jar)

        Jedis jedis=new Jedis("127.0.0.1",6379);
	jedis.set("s1", "redis java");
	System.out.println(jedis.get("s1"));
	System.out.println(jedis.keys("*"));

2、事务测试

        Jedis jedis=new Jedis("127.0.0.1",6379);
	Transaction tc=jedis.multi();
	tc.set("t1", "t1");
	tc.set("t2", "t2");
	tc.exec();

3、JedisPool
获得连接池工具类:

package redis;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
/**
 * 采用双重校验锁的单例模式
 *
 */
public class JedisPoolUtil {
private JedisPoolUtil(){}
//被volatile修饰的变量不会被本地线程缓存,对该变量的读写都是直接操作共享内存。
private static volatile JedisPool jedisPool=null;

//获得redis连接池
public static JedisPool getJedisPoolInstance(){
	if(jedisPool==null){
		synchronized (JedisPoolUtil.class) {
			if(jedisPool==null){
				 JedisPoolConfig poolConfig =new JedisPoolConfig();
				 poolConfig.setMaxActive(1000);
				 poolConfig.setMaxIdle(32);
				 poolConfig.setMaxWait(100*1000);
				 poolConfig.setTestOnBorrow(true);
				 
				 jedisPool = new JedisPool(poolConfig,"127.0.0.1",6379);
			}
		}
	}
	return jedisPool;
}

//释放redis
public static void release(JedisPool jedisPool,Jedis jedis){
	if(null != jedis){
		jedisPool.returnResourceObject(jedis);
	}
}
 
}

JedisPoolConfig配置说明:
maxActive:pool可分配最大jedis实例数,通过pool.getResource()来获取;如果赋值为-1,则表示不限制;如果pool已经分配了maxActive个jedis实例,则此时pool的状态为exhausted。
maxIdle:pool空闲最大jedis实例数;
maxWait:表示当获得一个jedis实例时,最大的等待时间,如果超过等待时间,则直接抛JedisConnectionException;
testOnBorrow:获得一个jedis实例的时候是否检查连接可用性(ping());如果为true,则得到的jedis实例均是可用的;
whenExhaustedAction:表示当pool中的jedis实例都被使用完时,pool要采取的操作;默认有三种。
 WHEN_EXHAUSTED_FAIL --> 表示无jedis实例时,直接抛出NoSuchElementException;
 WHEN_EXHAUSTED_BLOCK --> 则表示阻塞住,或者达到maxWait时抛出JedisConnectionException;
 WHEN_EXHAUSTED_GROW --> 则表示新建一个jedis实例,也就说设置的maxActive无用;

测试类:

public static void main(String[] args) {
	
	JedisPool jedisPool=JedisPoolUtil.getJedisPoolInstance();
	Jedis jedis=null;
	try {
	    jedis=jedisPool.getResource();
		jedis.set("s1", "test JedisPool");
		System.out.println(jedis.get("s1"));
		System.out.println(jedis.keys("*"));
	} catch (Exception e) {
		e.printStackTrace();
	}finally{
		JedisPoolUtil.release(jedisPool, jedis);
	}
}

八、redis与memcached区别

1、数据类型
memcached只支持String类型,redis数据类型更丰富

2、数据持久化
memcached不支持数据持久化,数据全部在内存中;redis支持RDB和AOF方式持久化

3、数据备份
redis支持master/slave主从复制,可以容灾备份

4、数据一致性
Memcache 在并发场景下,用cas保证一致性;redis事务支持比较弱,只能保证事务中的每个操作连续执行

九、redis 内存处理

1、redis 过期策略是:定期删除+惰性删除

定期删除:指的是 redis 默认是每隔 100ms 就随机抽取一些设置了过期时间的 key,检查其是否过期,如果过期就删除。(问题是,定期删除可能会导致很多过期 key 到了时间并没有被删除掉。使用惰性删除)

惰性删除:获取 key 的时候,如果此时 key 已经过期,就删除,不会返回任何东西。(问题:定期删除漏掉了很多过期 key,然后你也没及时去查,也就没走惰性删除。如果大量过期 key 堆积在内存里,导致 redis 内存块耗尽了。使用内存淘汰机制

2、内存淘汰机制

redis 内存淘汰机制有以下几个:

  • noeviction: 当内存不足以容纳新写入数据时,新写入操作会报错,这个一般没人用吧,实在是太恶心了。
  • allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 key(这个是最常用的)。
  • allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个 key,这个一般没人用吧,为啥要随机,肯定是把最近最少使用的 key 给干掉啊。
  • volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的 key(这个一般不太合适)。
  • volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个 key。
  • volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的 key 优先移除。

十、Redis 分布式锁

package com.vito.service;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

/**
 * Created by VitoYi on 2018/4/5.
 */
@Component
public class RedisLock {

    Logger logger = LoggerFactory.getLogger(this.getClass());

    @Autowired
    private StringRedisTemplate redisTemplate;

    /**
     * 加锁
     * @param key   商品id
     * @param value 当前时间+超时时间
     * @return
     */
    public boolean lock(String key, String value) {
        if (redisTemplate.opsForValue().setIfAbsent(key, value)) {     //这个其实就是setnx命令,只不过在java这边稍有变化,返回的是boolea
            return true;
        }

        //避免死锁,且只让一个线程拿到锁
        String currentValue = redisTemplate.opsForValue().get(key);
        //如果锁过期了
        if (!StringUtils.isEmpty(currentValue) && Long.parseLong(currentValue) < System.currentTimeMillis()) {
            //获取上一个锁的时间
            String oldValues = redisTemplate.opsForValue().getAndSet(key, value);

            /*
               只会让一个线程拿到锁
               如果旧的value和currentValue相等,只会有一个线程达成条件,因为第二个线程拿到的oldValue已经和currentValue不一样了
             */
            if (!StringUtils.isEmpty(oldValues) && oldValues.equals(currentValue)) {
                return true;
            }
        }
        return false;
    }


    /**
     * 解锁
     * @param key
     * @param value
     */
    public void unlock(String key, String value) {
        try {
            String currentValue = redisTemplate.opsForValue().get(key);
            if (!StringUtils.isEmpty(currentValue) && currentValue.equals(value)) {
                redisTemplate.opsForValue().getOperations().delete(key);
            }
        } catch (Exception e) {
            logger.error("『redis分布式锁』解锁异常,{}", e);
        }
    }
}

第二种核心代码:实现自旋等待 

/**
     * 加锁
     *
     * @param key
     * @param waitTime 等待时间,在这个时间内会多次尝试获取锁,超过这个时间还没获得锁,就返回false
     * @param interval 间隔时间,每隔多长时间尝试一次获的锁
     * @param expireTime key的过期时间
     */
    public Boolean lock(String key, Long waitTime,Long interval, Long expireTime) {

        String value = UUID.randomUUID().toString().replaceAll("-", "").toLowerCase();

        Boolean flag = setNx(key, value, expireTime, TimeUnit.MILLISECONDS);

        // 尝试获取锁 成功返回
        if (flag) {
            return flag;
        } else {
            // 现在时间
            long newTime = System.currentTimeMillis();
            // 等待过期时间
            long loseTime = newTime + waitTime;

            // 不断尝试获取锁成功返回
            while (System.currentTimeMillis() < loseTime) {

                Boolean testFlag = setNx(key, value, expireTime, TimeUnit.MILLISECONDS);
                if (testFlag) {
                    return testFlag;
                }

                try {
                    Thread.sleep(interval);
                } catch (InterruptedException e) {
                    log.error("获取锁异常",e);
                }
            }
        }
        return false;
    }

http://www.cnblogs.com/VitoYi/p/8726070.html

具体实现有封装的redisson

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值