缓存穿透、击穿、雪崩、分布式锁

1. 缓存穿透

缓存穿透是访问一个不存在的key,缓存不起作用,请求会穿透到DB,流量大时DB会挂掉。缓存击穿是访问一个存在的key,在缓存过期的一刻,同时有大量的请求,这些请求都会击穿到DB,造成瞬时DB请求量大、压力骤增。

比如用一个不存在的用户ID获取用户信息,不论数据库还是缓存都没有,如果大量的攻击会导致数据库崩溃

1.1. 解决方法

  1. 对空值缓存

    如果查询返回的数据为空(不管数据库是否存在),仍然把这个结果(null)进行缓存,给其设置一个很短的过期时间(30秒)

  1. 设置可访问的名单(白名单)

    使用redis中的bitmaps类型定一个可访问的名单,名单id作为偏移量,每次访问和bitmaps里面进行比较,如果访问的ID不存在,不允许访问

  1. 采用布隆过滤器

    布隆过滤器:实际上是一个很长的二进制向量(位图)和一系列随机映射函数(哈希函数)

    布隆过滤器可以用于检测一个元素是否在一个集合中,它的优点是空间效率和查询的时间都远超过一般的算法,缺点是有一定的误判和删除困难。将所有可能存在的数据哈希到一个足够到的bitmaps中,一个一定不存在的数据会被这个bitmaps拦截掉,从而避免了对底层存储系统的查询压力。

  2. 进行实时监控

    当发现redis的命中率降低,需要排查访问对象和访问的数据,和运维人员配合可以设置黑名单限制对其提供服务(IP黑名单等)

2. 缓存击穿

redis中某个热点key过期,此时大量的请求同时过来,发现没有命中缓存,请求都打到了db上,导致db压力瞬间大增,可能会造成数据库崩溃

缓存击穿出现的现象:

  • 数据库访问压力瞬间增大

  • redis里面没有出现大量的key过期

  • redis正常运行

2.1. 解决方法

  1. 预先设置热门数据,适时调整过期时间

    在redis高峰之前,把一些热门数据提前存入到redis里面,对缓存中的热门数据进行监控,实时调整过期时间

  1. 使用锁

    缓存中拿不到数据时,此时不是立即取db中查询,而是去获取分布式锁(如redis中的setnx),拿到了锁再去db获取数据,没有拿到锁的线程休眠一段时间再重试获取数据的方法

3. 缓存雪崩

key对应的数据存在,但是极短的时间内有大量的key集中过期,此时若有大量的并发请求过来,发现缓存没有数据,大量的请求会落到db上去加载数据,导致数据库服务器崩溃

缓存雪崩和缓存击穿的区别在于:前者时大量的key集中过期,后者时某个热点key过期

3.1. 解决方案

  1. 构建多级缓存

    nginx缓存 + redis 缓存 + 其他缓存(ehcache等)

  2. 使用锁或者队列

    加锁或者队列的方式来保证不及有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上,不适用高并发的情况

  3. 监控缓存过期,提前更新

    监控缓存,发现缓存快过期来,提前对缓存进行更新

  4. 缓存失效时间分散

    在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样缓存的过期时间重复率就会降低,减少集体失效的事件

4. 分布式锁

随着业务发展的需要,原单体单机部署的系统被演化为分布式集群系统后,由于分布式系统多线程、多进程且分布在不同的机器上,这使原单机部署情况下的并发控制锁策略失效
单纯的Java API并不能提供分布式锁的能力,为了解决这个问题就需要一种跨JVM的互斥机制来控制共享资源的访问,这就是分布式锁要解决的问题

4.1. 分布式锁主流的实现方案

  • 基于数据库实现分布式锁

  • 基于缓存(redis等)

  • 基于zookeeper

每一种分布式锁解决方案各有优点

  • 性能:redis最高

  • 可靠性:zookeeper最高

4.2. 使用redis实现分布式锁

需要使用下面这个命令来实现分布式锁:

1
2
3
4
set key value NX PX <times>

# time:有效期(毫秒)
# 当key不存在的时候,设置其值为value,并且同时设置有效期
  • NX:当数据库中key不存在时,可以将key-value添加到数据库
  • XX:当数据库中key存在时,可以将key-value添加数据库,与NX参数互斥
  • EX:key的超时秒数
  • PX:key的超时毫秒数,与EX互斥

示例:

1
set user:1:info "ok" NX PX 10000

1. 上锁的过程

执行set key value NX PX 有效期(毫秒) 命令,返回OK表示执行成功,获取锁成功,多个客户端并发执行此命令时,redis可以保证只有一个执行成功

2. 为什么要设置过期时间?

客户端获取锁后,由于系统问题,如果宕机来,会导致锁无法释放(死锁),其他客户端就无法获取锁,所以要指定一个过期时间

3. 设置有效期时间控制

比如有效期设置10秒,但是业务处理时间大于10s,导致还没有处理完,锁就释放了,其他客户端就会进来,这种情况需要引入看门狗机制来解决这个问题

4. 解决锁误删的问题

锁存在误删的情况:所谓误删就是自己把别人持有的锁给删掉了。

比如线程A获取锁的时候,设置的有效期是10秒,但是执行业务的时候,A程序突然卡主了超过了10秒,此时这个锁就可能被其他线程拿到,比如被线程B拿到了,然后A从卡顿中恢复了,继续执行业务,业务执行完毕之后,去执行了释放锁的操作,此时A会执行del命令,此时就出现了锁的误删,导致的结果就是把B持有的锁给释放了,然后其他线程又会获取这个锁,挺严重的。

解决方法:获取锁的之前,生成一个全局唯一id,将这个id也丢到key对应的value中,释放锁之前,从redis中将这个id拿出来和本地的比较一下,看看是不是自己的id,如果是的再执行del释放锁的操作

5. 还是存在误删的可能(原子操作问题)

刚才上面说了,del之前,会先从redis中读取id,然后和本地id对比一下,如果一致,则执行删除,伪代码如下

1
2
step1:判断 redis.get("key").id==本地id 是否相当,如果是则执行step2
step2:del key;

此时如果执行step2的时候系统卡主了,比如卡主了10秒,然后redis才收到,这个期间锁可能又被其他线程获取了,此时又发生了误删的操作。

这个问题的根本原因是:判断和删除这2个步骤对redis来说不是原子操作导致的,这个时候就需要使用Lua脚本来解决。

6. Lua脚本来释放锁
将复杂的或者多步的redis操作,写为一个脚本,一次提交给redis执行,减少反复连接redis的次数,提升性能。Lua脚本类似于redis事务,有一定的原子性,不会被其他命令插队,可以完成一些redis事务的操作。

但是注意redis的LUA脚本功能,只能在redis2.6以上版本才能使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Arrays;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@RestController
public class LockTest {
@Autowired
private RedisTemplate<String, String> redisTemplate;
@RequestMapping(value = "/lock", produces = MediaType.TEXT_PLAIN_VALUE)
public String lock() {
String lockKey = "k1";
String uuid = UUID.randomUUID().toString();
//1.获取锁,有效期10秒
if (this.redisTemplate.opsForValue().setIfAbsent(lockKey, uuid, 10, TimeUnit.SECONDS)) {
//2.执行业务
// todo 业务
//3.使用Lua脚本释放锁(可防止误删)
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);
Long result = redisTemplate.execute(redisScript, Arrays.asList(lockKey), uuid);
System.out.println(result);
return "获取锁成功!";
} else {
return "加锁失败!";
}
}
}

7. 分布式锁总结

为了确保分布式锁可用,至少需要确保分布式锁的实现同时满足以下4个条件:

  • 互斥性:在任意时刻只能有一个客户端持有锁

  • 不会死锁:即有一个客户端在持有锁期间崩溃而没有释放锁,也能够保证后续其他客户端能够获取锁

  • 加锁和解锁必须时同一个客户端,客户端不能把别人的锁解除了

  • 加锁和解锁必须有原子性

5. 分布式锁实现Demo

  1. 引入pom

    1
    2
    3
    4
    5
    6
      <!-- Redis客户端 start -->
    <dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.9.0</version>
    </dependency>
  2. Controller层

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    /**
    * 车主端 确认支付
    *
    * @param requestDto
    * @param rpcPacket
    * @return
    */
    @UrlMapping(url = "carOwner/confirmPayment")
    public RpcPacket changeConfirmPaymentForCarOwner(RpcCarOwnerConfirmPaymentReqDto requestDto, RpcPacket rpcPacket) {
    logger.info("车主端 确认支付,requestDto {},rpcPacket {}", JsonUtil.toJson(requestDto), JsonUtil.toJson(rpcPacket));
    RpcPacket packet = new RpcPacket();
    String lockKey = OrderServiceModulConstant.LOCK_CAROWNER_COMFIRM_PAY_LOKENAME + requestDto.getOrderId();
    String lockName = "";
    try {
    //先分布锁一下 订单
    lockName = redisCache.getLockLua(lockKey, OrderServiceModulConstant.LOCK_CAROWNER_COMFIRM_PAY_EXPIRETIME, OrderServiceModulConstant.LOCK_CAROWNER_COMFIRM_PAY_TIMEOUT);
    if (!StringUtil.isEmpty(lockName)) {
    Long oldCouponId = orderFeeService.updateBindCounpByOrderId(requestDto);
    // if (oldCouponId!=null) {
    // rpcServie.updateUnBindCouponByCouponId(oldCouponId);
    // }
    RpcCarOwnComfirmPayRetDto res = orderFeeService.changeConfirmPaymentForCarOwner(requestDto, rpcPacket);
    packet.setData(res);
    if (res != null) {
    switch (res.getStatus()) {
    case 1:
    packet.setAnwserCode(new AnwserCode(1, "车主端 支付成功"));
    break;
    case 2:
    packet.setAnwserCode(new AnwserCode(1, "车主端 请调起三方支付"));
    break;
    case 3:
    packet.setAnwserCode(new AnwserCode(-2, "车主端 支付失败"));
    break;
    }
    }
    } else {
    throw new ArgsException("支付处理中,请勿重复提交");
    }
    } catch (ArgsException e) {
    logger.error("车主端 确认支付异常 {}", e.toString());
    packet.setAnwserCode(e.getAnwserCode());
    } catch (Exception e) {
    logger.error("车主端 确认支付异常 {}", e.toString());
    packet.setAnwserCode(OrderServiceAnwserCode.BUSS_ERROR_CALCULPAYMENT_CAROWNER);
    } finally {
    redisCache.releaseLock(lockKey, lockName);
    }
    logger.info("车主端 确认支付,packet {}", JsonUtil.toJson(packet));
    return packet;
    }

3
. 编写工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import redis.clients.jedis.*;
import redis.clients.jedis.exceptions.JedisException;
import redis.clients.jedis.params.geo.GeoRadiusParam;

import java.io.IOException;
import java.util.*;

public class RedisCache {

private final static Logger logger = LoggerFactory.getLogger(RedisCache.class);

private JedisCluster jedisCluster;

private String prefixKey;

@Autowired
private RedisConfig redisConfig;

public RedisCache(String prefixKey, RedisConfig redisConfig) {
this.prefixKey = prefixKey + "_";
this.redisConfig = redisConfig;
this.init();
}

public void init() {
if (null == jedisCluster) {
if (StringUtils.isEmpty(prefixKey) || prefixKey.length() <= 1) {
logger.error("初始化redisCache失败,该模块的redis的key为空");

System.exit(0);
}
//加锁
synchronized (RedisCache.class) {
if (null == jedisCluster) {
JedisPoolConfig poolConfig = new JedisPoolConfig();
Set<HostAndPort> shardInfoSet = new HashSet<>(16);
try {
//配置
poolConfig.setMaxIdle(redisConfig.getMaxIdle());
poolConfig.setMinIdle(redisConfig.getMinIdle());
poolConfig.setTestOnReturn(redisConfig.getTestOnReturn());
poolConfig.setTestOnBorrow(redisConfig.getTestOnBorrow());

String[] shardList = redisConfig.getShared().split(";");
for (String server : shardList) {
String[] values = server.split(":");
HostAndPort node = new HostAndPort(values[0], Integer.parseInt(values[1]));
shardInfoSet.add(node);
}
int timeout = redisConfig.getTimeout() == null ? 2000 : redisConfig.getTimeout();
String password = redisConfig.getPassword();
Integer maxActive = redisConfig.getMaxActive();
//是否有密码
if (StringUtils.isNotBlank(password)) {
jedisCluster = new JedisCluster(shardInfoSet, timeout, timeout, maxActive, poolConfig);
} else {
jedisCluster = new JedisCluster(shardInfoSet, timeout, timeout, maxActive, password, poolConfig);
}
} catch (Exception e) {
logger.error("getSharedJedisPool", e);
jedisCluster = null;
throw e;
}
}
}
}
}

/**
* 以新换旧,设置新值同时返回旧值
*
* @param key key
* @param value 新值
* @return 旧值
*/
public String getSet(String key, String value) {
String realKey = this.prefixKey + key;
try {
return jedisCluster.getSet(realKey, value);
} catch (Exception ex) {
throw ex;
}
}

/**
* 判断key是否存在
*
* @param key key
* @return 存在与否
*/
public Boolean exists(String key) {
String realKey = this.prefixKey + key;
try {
return jedisCluster.exists(realKey);
} catch (Exception ex) {
throw ex;
}
}

/**
* 删除key
*
* @param key key
* @return 结果
*/
public Boolean deleteKey(String key) {
String realKey = this.prefixKey + key;
try {
return jedisCluster.del(realKey) > 0;
} catch (Exception ex) {
throw ex;
}
}

/**
* 删除不含前缀的key
*
* @param key key
*/
public Boolean deleteKeyNoPrefixKey(String key) {
try {
return jedisCluster.del(key) > 0;
} catch (Exception ex) {
throw ex;
}
}

/**
* 设置带有过期时间的key-value
*
* @param key key
* @param value 值
* @param seconds 有效时间
* @return 结果
*/
public boolean set(String key, String value, Integer seconds) {
String realKey = this.prefixKey + key;
try {
jedisCluster.setex(realKey, seconds, value);
return true;
} catch (Exception ex) {
throw ex;
}
}

/**
* 设置key-value
*
* @param key key
* @param value 值
*/
public boolean setNoExpire(String key, String value) {
String prefixKey = this.prefixKey + key;
try {
jedisCluster.set(prefixKey, value);
return true;
} catch (Exception ex) {
throw ex;
}
}

/**
* 根据key获取value
*
* @param key key
* @return 结果
*/
public String get(String key) {
String realKey = this.prefixKey + key;
try {
return jedisCluster.get(realKey);
} catch (Exception ex) {
throw ex;
}
}

/**
* 查询key的过期时间还剩多少秒
*
* @param key key
* @return 生育过期时间
*/
public Long ttl(String key) {
String realKey = this.prefixKey + key;
try {
return jedisCluster.ttl(realKey);
} catch (Exception ex) {
throw ex;
}
}

/**
* 设置过期时间
*
* @param key key
* @param seconds 过期时间
* @return 结果
*/
public boolean cacheExpire(String key, Integer seconds) {
String realKey = this.prefixKey + key;
try {
jedisCluster.expire(realKey, seconds);
return true;
} catch (Exception ex) {
throw ex;
}
}

/**
* 获取序列化对象
*
* @param key key
* @return 结果
*/
public byte[] get(byte[] key) {
String realKey = this.prefixKey + new String(key);
try {
return jedisCluster.get(realKey).getBytes();
} catch (Exception ex) {
throw ex;
}
}

/**
* 自增1, value必须是数值型,否则报错
*
* @param key key
* @return 自增结果
*/
public Long incr(String key) {
String realKey = this.prefixKey + key;
try {
return jedisCluster.incr(realKey);
} catch (Exception ex) {
throw ex;
}
}

/**
* 增加值, value必须是数值型,否则报错
*
* @param key key
* @param integer 增加的值
* @return 自增结果
*/
public Long incrBy(String key, long integer) {
String realKey = this.prefixKey + key;
try {
return jedisCluster.incrBy(realKey, integer);
} catch (Exception ex) {
throw ex;
}
}

/**
* 自减1, value必须是数值型,否则报错
*
* @param key key
* @return 自增结果
*/
public Long decr(String key) {
String realKey = this.prefixKey + key;
try {
return jedisCluster.decr(realKey);
} catch (Exception ex) {
throw ex;
}
}

/**
* 减少值, value必须是数值型,否则报错
*
* @param key key
* @param integer 减少的值
* @return 减少后的结果
*/
public Long decrBy(String key, long integer) {
String realKey = this.prefixKey + key;
try {
return jedisCluster.decrBy(realKey, integer);
} catch (Exception ex) {
throw ex;
}
}

/**
* 新建LIST
*
* @param key
* @param index
* @param value
* @return
*/
public boolean setList(String key, Long index, String value) {
String realKey = this.prefixKey + key;
if (value == null) {
return false;
}
try {
jedisCluster.lset(realKey, index, value);
return true;

} catch (Exception e) {
throw e;
}
}


/**
* 设置hash 字段-值
*
* @param key 键
* @param field 字段
* @param value 值
* @return
*/
public boolean setHash(String key, String field, String value) {
String realKey = this.prefixKey + key;
if (value == null) {
return false;
}
try {
jedisCluster.hset(realKey, field, value);
return true;
} catch (Exception e) {
throw e;
}
}

/**
* 获得HashSet对象
*
* @param key 键
* @param field 字段
* @return Json String or String value
*/
public String getHash(String key, String field) {
String realKey = this.prefixKey + key;
try {
return jedisCluster.hget(realKey, field);
} catch (Exception e) {
throw e;
}
}

/**
* 存储多个值-- 存储 map
*
* @param key key
* @param map value集合
* @return
*/
public boolean hmset(String key, Map<String, String> map) {
String realKey = this.prefixKey + key;
try {
jedisCluster.hmset(realKey, map);
return true;
} catch (Exception e) {
throw e;
}
}

/**
* 获取多个key对应的值
*
* @param key
* @param fields
* @return
*/
public List<String> hmget(String key, String... fields) {
String realKey = this.prefixKey + key;
try {
return jedisCluster.hmget(realKey, fields);
} catch (Exception e) {
throw e;
}
}

/**
* 删除Hash 字段对象
*
* @param key 键
* @param field 字段
* @return 删除的记录数
*/
public long delHash(String key, String field) {
String realKey = this.prefixKey + key;
try {
return jedisCluster.hdel(realKey, field);
} catch (Exception e) {
throw e;
}
}

/**
* 删除Hash 多个字段对象
*
* @param key 键值
* @param field 字段
* @return 删除的记录数
*/
public long delHash(String key, String... field) {
String realKey = this.prefixKey + key;

try {
return jedisCluster.hdel(realKey, field);
} catch (Exception e) {

throw e;
}
}

/**
* 判断key下的field是否存在
*
* @param key 键
* @param field 字段
* @return
*/
public boolean existsHash(String key, String field) {
String realKey = this.prefixKey + key;
try {
return jedisCluster.hexists(realKey, field);

} catch (Exception e) {

throw e;
}
}

/**
* 返回 key 指定的哈希集中所有字段的value值
*
* @param key
* @return
*/
public List<String> hvals(String key) {
String realKey = this.prefixKey + key;
try {
return jedisCluster.hvals(realKey);
} catch (Exception e) {

throw e;
}
}

/**
* 列出所有的filed
* hkeys key
*
* @param key key
* @return
*/
public Set<String> hkeys(String key) {
String realKey = this.prefixKey + key;
try {
return jedisCluster.hkeys(realKey);
} catch (Exception e) {
throw e;
}
}

/**
* 返回field的数量
*
* @param key
* @return
*/
public long lenHash(String key) {
String realKey = this.prefixKey + key;
try {
return jedisCluster.hlen(realKey);
} catch (Exception e) {
throw e;
}
}

/**
* list 添加
*
* @param key
* @param strings
*/
public Long lpush(String key, String... strings) {
String realKey = this.prefixKey + key;
return jedisCluster.lpush(realKey, strings);
}

/**
* list 获取长度
*/
public long llen(String key) {
String realKey = this.prefixKey + key;
return jedisCluster.llen(realKey);
}

//redis 监听消息通道===========================

/**
* 推入消息到redis消息通道
*
* @param String channel
* @param String message
*/
public Long publish(String channel, String message) {
return jedisCluster.lpush(channel, message);
}

/**
* 推入消息到redis消息通道
*
* @param byte[] channel
* @param byte[] message
*/
public Long publish(byte[] channel, byte[] message) {
return jedisCluster.lpush(channel, message);
}

/**
* 获取队列数据
*
* @param byte[] key 键名
* @return
*/
public byte[] rpop(byte[] key) {
String realKey = new String(key);
realKey = this.prefixKey + key;
byte[] bytes = null;
try {
bytes = jedisCluster.rpop(realKey.getBytes());
} catch (Exception e) {
//释放redis对象
e.printStackTrace();
}
return bytes;
}

/**
* 获取队列数据
*
* @param byte[] key 键名
* @return
*/
public String rpop(String key) {
String realKey = this.prefixKey + key;
String bytes = null;
try {
bytes = jedisCluster.rpop(realKey);
} catch (Exception e) {
//释放redis对象
e.printStackTrace();
}
return bytes;
}

/**
* 监听消息通道
*
* @param jedisPubSub - 监听任务
* @param channels - 要监听的消息通道
* @throws IOException
*/
public void subscribe(BinaryJedisPubSub jedisPubSub, byte[]... channels) throws IOException {
try {
jedisCluster.subscribe(jedisPubSub, channels);
} catch (Exception e) {
throw e;
} finally {
jedisCluster.close();
}
}

/**
* 监听消息通道
*
* @param jedisPubSub - 监听任务
* @param channels - 要监听的消息通道
* @throws IOException
*/
public void subscribe(JedisPubSub jedisPubSub, String... channels) throws IOException {
try {
jedisCluster.subscribe(jedisPubSub, channels);
} catch (Exception e) {
throw e;
} finally {
jedisCluster.close();
}
}

//redis 监听消息通道===========================

/**
* 删除指定元素
*
* @param key
* @param count
* @param value
*/
public boolean lrem(String key, int count, String value) {
String realKey = this.prefixKey + key;
try {
jedisCluster.lrem(realKey, count, value);
return true;
} catch (Exception e) {
throw e;
}
}

/**
* list 指定元素 0,-1 所有
*
* @param key
* @param start
* @param end
* @return
*/
public List<String> lrange(String key, int start, int end) {
String realKey = this.prefixKey + key;
try {
return jedisCluster.lrange(realKey, start, end);
} catch (Exception e) {
throw e;
}
}

/**
* add string to set
*
* @param key
* @param members
*/
public boolean sadd(String key, String... members) {
String realKey = this.prefixKey + key;
try {
jedisCluster.sadd(realKey, members);
return true;
} catch (Exception e) {
throw e;
}
}

/**
* 判断集合中是否存在某个值
*
* @param key
* @param member
* @return
*/
public boolean sismember(String key, String member) {
String realKey = this.prefixKey + key;
try {
return jedisCluster.sismember(realKey, member);
} catch (Exception e) {
throw e;
}
}

/**
* select all
*
* @param key
* @return
*/
public Set<String> smembers(String key) {
String realKey = this.prefixKey + key;
try {
return jedisCluster.smembers(realKey);
} catch (Exception e) {
throw e;
}
}

/**
* delete value in set
*
* @param key
* @param value
*/
public boolean srem(String key, String value) {
String realKey = this.prefixKey + key;
try {
return jedisCluster.srem(realKey, value) > 0;
} catch (Exception e) {
throw e;
}
}

public String ltrim(String key, int start, int end) {
try {
return jedisCluster.ltrim(this.prefixKey + key, start, end);
} catch (Exception e) {
throw e;
}
}

/**
* score降序,获取指定索引范围的元素
* @param key
* @param start
* @param end
* @return
*/
public Set<String> zrevrange(String key, long start, long end) {
String realKey = this.prefixKey + key;
try {
return jedisCluster.zrevrange(realKey, start, end);
} catch (Exception e) {
throw e;
}
}

public boolean setNx(String key, String obj, int seconds) {
Long result;
try {
result = jedisCluster.setnx(key, obj);
if (result == 1) {
jedisCluster.expire(key, seconds);
return true;
} else {
return false;
}
} catch (Exception e) {
}
return false;
}

/**
* 添加元素---set
* @param key
* @param score
* @param value
* @return
*/
public Long zadd(String key, Long score, String value) {
String realKey = this.prefixKey + key;
Long result = 0L;
try {
result = jedisCluster.zadd(realKey, score.doubleValue(), value);
} catch (Exception e) {
}
return result;
}

/**
* 分页获取ZSET数据
*
* @param key
* @param start
* @param end
* @return
*/
public Set<Tuple> zrevrangeWithScores(String key, long start, long end) {
logger.info("RedisCache zrange key={},start={},end={}", key, start, end);
String realKey = this.prefixKey + key;
try {
return jedisCluster.zrevrangeWithScores(realKey, start, end);
} catch (Exception e) {
logger.error("RedisCache zrange key={},start={},end={},e={}", realKey, start, end, e);
}
return Collections.emptySet();
}

/**
* 获取对应元素的排名
*
* @param key
* @param member
* @return
*/
public Long zrevrank(String key, String member) {
logger.info("RedisCache zrevrank key={},member={}", key, member);
String realKey = this.prefixKey + key;
try {
return jedisCluster.zrevrank(realKey, member);
} catch (Exception e) {
logger.error("RedisCache zrevrank key={},member={},e ={}", key, member, e);
}
return -1L;
}

/**
* 获取对应元素的分数
*
* @param key
* @param member
* @return
*/
public Double zscore(String key, String member) {
logger.info("RedisCache zscore key={},member={}", key, member);
String realKey = this.prefixKey + key;
try {
return jedisCluster.zscore(realKey, member);
} catch (Exception e) {
logger.error("RedisCache zscore key={},member={},e ={}", key, member, e);
}
return 0.00;
}


public Long zrem(String key, String value) {
String realKey = this.prefixKey + key;
Long result = 0L;
try {
result = jedisCluster.zrem(realKey, value);
} catch (Exception e) {
}
return result;
}

/**
* 加锁
*
* @param key 锁的key
* @param acquireTimeout 获取超时时间
* @param timeout 锁的超时时间
* @return 锁标识
*/
public String lockWithTimeout(String key, long acquireTimeout, long timeout) {
String retIdentifier = null;
try {
// 随机生成一个value
String identifier = UUID.randomUUID().toString();

// 超时时间,上锁后超过此时间则自动释放锁
int lockExpire = (int) (timeout / 1000);

// 获取锁的超时时间,超过这个时间则放弃获取锁
long end = System.currentTimeMillis() + acquireTimeout;
while (System.currentTimeMillis() < end) {
if (jedisCluster.setnx(key, identifier) == 1) {
jedisCluster.expire(key, lockExpire);
// 返回value值,用于释放锁时间确认
retIdentifier = identifier;
return retIdentifier;
}
// 返回-1代表key没有设置超时时间,为key设置一个超时时间
if (jedisCluster.ttl(key) == -1) {
jedisCluster.expire(key, lockExpire);
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
} catch (JedisException e) {
throw e;
}
return retIdentifier;
}

/**
* 加锁
* 运用lua脚本
*
* @param key 锁的key
* @param acquireTimeout 获取超时时间
* @param timeout 锁的超时时间
* @return 锁标识
*/
public String getLockLua(String key, long acquireTimeout, long timeout) {
return getLockLua(key, timeout);
}

public String getLockLua(String key, long timeout) {
List<String> keys = new ArrayList<>();
int lockExpire = (int) (timeout / 1000);
keys.add(key);
List<String> args = new ArrayList<>();
args.add(lockExpire + "");
args.add(UUID.randomUUID().toString());
return "OK".equals(jedisCluster.eval("return redis.call('set', KEYS[1],ARGV[2],'nx', 'ex', ARGV[1]) ", keys, args)) ? args.get(1) : "";
}

public String getLock(String key, long timeout) {
String retIdentifier = null;
try {
// 随机生成一个value
String identifier = UUID.randomUUID().toString();

// 超时时间,上锁后超过此时间则自动释放锁
int lockExpire = (int) (timeout / 1000);

if (jedisCluster.setnx(key, identifier) == 1) {
jedisCluster.expire(key, lockExpire);
// 返回value值,用于释放锁时间确认
retIdentifier = identifier;
return retIdentifier;
}
// 返回-1代表key没有设置超时时间,为key设置一个超时时间
if (jedisCluster.ttl(key) == -1) {
jedisCluster.expire(key, lockExpire);
}
} catch (JedisException e) {
throw e;
}
return retIdentifier;
}

/**
* 释放锁
*
* @param key 锁的key
* @param identifier 释放锁的标识
* @return
*/
public boolean releaseLock(String key, String identifier) {
boolean retFlag = false;
try {
// 通过前面返回的value值判断是不是该锁,若是该锁,则删除,释放锁
if (identifier.equals(jedisCluster.get(key))) {
jedisCluster.del(key);
retFlag = true;
}
} catch (JedisException e) {
throw e;
}
return retFlag;
}

public Set<String> keys(String pattern) {
HashSet<String> keys = new HashSet<String>();

Map<String, JedisPool> clusterNodes = jedisCluster.getClusterNodes();
for (String node : clusterNodes.keySet()) {
JedisPool jp = clusterNodes.get(node);
Jedis connection = jp.getResource();
try {
keys.addAll(connection.keys(pattern));
} catch (Exception e) {

} finally {
connection.close();
}
}

return keys;
}

//订单开始坐标存入redis
public Long addReo(double lon, double lat, String orderId) {
try {
return jedisCluster.geoadd("orderStation", lon, lat, orderId);
} catch (Exception e) {
logger.error("reids 缓存坐标异常:", e);
}
return null;
}

/**
* 查询坐标系附近的订单
*
* @param lon 经度
* @param lat 纬度
* @param radius 半径
* @return 结果
*/
public List<GeoRadiusResponse> queryReo(Double lon, Double lat, double radius) {
try {
return jedisCluster.georadius("orderStation", lon, lat, radius, GeoUnit.KM, GeoRadiusParam.geoRadiusParam().withDist());
} catch (Exception e) {
logger.error("reids 查询坐标系附近的订单异常:", e);
}
return null;
}

/**
* redis删除订单坐标
*
* @param orderId 订单号
* @return 结果
*/
public Long delReo(String orderId) {
try {
return jedisCluster.zrem("orderStation", orderId);
} catch (Exception e) {
logger.error("reids 删除订单坐标异常:", e);
}
return null;
}

public JedisCluster getJedisCluster() {
return jedisCluster;
}
}