Java SpringBoot Redis

一、Jedis,Redisson,Lettuce 三者的区别

共同点:都提供了基于 Redis 操作的 Java API,只是封装程度,具体实现稍有不同。
不同点:

  • 1.1、Jedis

是 Redis 的 Java 实现的客户端。支持基本的数据类型如:String、Hash、List、Set、Sorted Set。
特点:使用阻塞的 I/O,方法调用同步,程序流需要等到 socket 处理完 I/O 才能执行,不支持异步操作。Jedis 客户端实例不是线程安全的,需要通过连接池来使用 Jedis。

  • 1.1、Redisson

优点点:分布式锁,分布式集合,可通过 Redis 支持延迟队列。

  • 1.3、 Lettuce

用于线程安全同步,异步和响应使用,支持集群,Sentinel,管道和编码器。
基于 Netty 框架的事件驱动的通信层,其方法调用是异步的。Lettuce 的 API 是线程安全的,所以可以操作单个 Lettuce 连接来完成各种操作。

二、Jedis

三、RedisTemplate

3.1、使用配置

Maven配置引入,(要加上版本号,这里是因为 Parent 已声明)

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-data-redis</artifactId>
  4. </dependency>

application-dev.yml

  1. spring:
  2. redis:
  3. host: 192.168.1.140
  4. port: 6379
  5. password:
  6. database: 15 # 指定redis的分库(共16个0到15)

3.2、使用示例

  1. @Override
  2. public CustomersEntity findById(Integer id) {
  3. // 需要缓存
  4. // 所有涉及的缓存都需要删除,或者更新
  5. try {
  6. String toString = stringRedisTemplate.opsForHash().get(REDIS_CUSTOMERS_ONE, id + "").toString();
  7. if (toString != null) {
  8. return JSONUtil.toBean(toString, CustomersEntity.class);
  9. }
  10. } catch (Exception e) {
  11. e.printStackTrace();
  12. }
  13. // 缓存为空的时候,先查,然后缓存redis
  14. Optional<CustomersEntity> byId = customerRepo.findById(id);
  15. if (byId.isPresent()) {
  16. CustomersEntity customersEntity = byId.get();
  17. try {
  18. stringRedisTemplate.opsForHash().put(REDIS_CUSTOMERS_ONE, id + "", JSONUtil.toJsonStr(customersEntity));
  19. } catch (Exception e) {
  20. e.printStackTrace();
  21. }
  22. return customersEntity;
  23. }
  24. return null;
  25. }

3.3、扩展

3.3.1、spring-boot-starter-data-redis 的依赖包

image.png

3.3.2、stringRedisTemplate API(部分展示)

opsForHash —> hash 操作
opsForList —> list 操作
opsForSet —> set 操作
opsForValue —> string 操作
opsForZSet —> Zset 操作
image.png

3.3.3、StringRedisTemplate 默认序列化机制

  1. public class StringRedisTemplate extends RedisTemplate<String, String> {
  2. /**
  3. * Constructs a new <code>StringRedisTemplate</code> instance. {@link #setConnectionFactory(RedisConnectionFactory)}
  4. * and {@link #afterPropertiesSet()} still need to be called.
  5. */
  6. public StringRedisTemplate() {
  7. RedisSerializer<String> stringSerializer = new StringRedisSerializer();
  8. setKeySerializer(stringSerializer);
  9. setValueSerializer(stringSerializer);
  10. setHashKeySerializer(stringSerializer);
  11. setHashValueSerializer(stringSerializer);
  12. }
  13. }

四、RedissonClient 操作示例

4.1 基本配置

4.1.1、Maven pom 引入

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-data-redis</artifactId>
  4. </dependency>
  5. <dependency>
  6. <groupId>org.redisson</groupId>
  7. <artifactId>redisson</artifactId>
  8. <version>3.8.2</version>
  9. <optional>true</optional>
  10. </dependency>
  11. <dependency>
  12. <groupId>org.redisson</groupId>
  13. <artifactId>redisson-spring-boot-starter</artifactId>
  14. <version>LATEST</version>
  15. </dependency>

4.1.2、添加配置文件 Yaml 或者 json 格式

redisson-config.yml

  1. # Redisson 配置
  2. singleServerConfig:
  3. address: "redis://192.168.1.140:6379"
  4. password: null
  5. clientName: null
  6. database: 15 #选择使用哪个数据库0~15
  7. idleConnectionTimeout: 10000
  8. pingTimeout: 1000
  9. connectTimeout: 10000
  10. timeout: 3000
  11. retryAttempts: 3
  12. retryInterval: 1500
  13. reconnectionTimeout: 3000
  14. failedAttempts: 3
  15. subscriptionsPerConnection: 5
  16. subscriptionConnectionMinimumIdleSize: 1
  17. subscriptionConnectionPoolSize: 50
  18. connectionMinimumIdleSize: 32
  19. connectionPoolSize: 64
  20. dnsMonitoringInterval: 5000
  21. #dnsMonitoring: false
  22. threads: 0
  23. nettyThreads: 0
  24. codec:
  25. class: "org.redisson.codec.JsonJacksonCodec"
  26. transportMode: "NIO"

或者,配置 redisson-config.json

  1. {
  2. "singleServerConfig": {
  3. "idleConnectionTimeout": 10000,
  4. "pingTimeout": 1000,
  5. "connectTimeout": 10000,
  6. "timeout": 3000,
  7. "retryAttempts": 3,
  8. "retryInterval": 1500,
  9. "reconnectionTimeout": 3000,
  10. "failedAttempts": 3,
  11. "password": null,
  12. "subscriptionsPerConnection": 5,
  13. "clientName": null,
  14. "address": "redis://192.168.1.140:6379",
  15. "subscriptionConnectionMinimumIdleSize": 1,
  16. "subscriptionConnectionPoolSize": 50,
  17. "connectionMinimumIdleSize": 10,
  18. "connectionPoolSize": 64,
  19. "database": 0,
  20. "dnsMonitoring": false,
  21. "dnsMonitoringInterval": 5000
  22. },
  23. "threads": 0,
  24. "nettyThreads": 0,
  25. "codec": null,
  26. "useLinuxNativeEpoll": false
  27. }

4.1.3、读取配置

新建读取配置类

  1. @Configuration
  2. public class RedissonConfig {
  3. @Bean
  4. public RedissonClient redisson() throws IOException {
  5. // 两种读取方式,Config.fromYAML 和 Config.fromJSON
  6. // Config config = Config.fromJSON(RedissonConfig.class.getClassLoader().getResource("redisson-config.json"));
  7. Config config = Config.fromYAML(RedissonConfig.class.getClassLoader().getResource("redisson-config.yml"));
  8. return Redisson.create(config);
  9. }
  10. }

或者,在 application.yml 中配置如下

  1. spring:
  2. redis:
  3. redisson:
  4. config: classpath:redisson-config.yaml

4.2 使用示例

  1. @RestController
  2. @RequestMapping("/")
  3. public class TeController {
  4. @Autowired
  5. private RedissonClient redissonClient;
  6. static long i = 20;
  7. static long sum = 300;
  8. // ========================== String =======================
  9. @GetMapping("/set/{key}")
  10. public String s1(@PathVariable String key) {
  11. // 设置字符串
  12. RBucket<String> keyObj = redissonClient.getBucket(key);
  13. keyObj.set(key + "1-v1");
  14. return key;
  15. }
  16. @GetMapping("/get/{key}")
  17. public String g1(@PathVariable String key) {
  18. // 设置字符串
  19. RBucket<String> keyObj = redissonClient.getBucket(key);
  20. String s = keyObj.get();
  21. return s;
  22. }
  23. // ========================== hash =======================-=
  24. @GetMapping("/hset/{key}")
  25. public String h1(@PathVariable String key) {
  26. Ur ur = new Ur();
  27. ur.setId(MathUtil.randomLong(1,20));
  28. ur.setName(key);
  29. // 存放 Hash
  30. RMap<String, Ur> ss = redissonClient.getMap("UR");
  31. ss.put(ur.getId().toString(), ur);
  32. return ur.toString();
  33. }
  34. @GetMapping("/hget/{id}")
  35. public String h2(@PathVariable String id) {
  36. // hash 查询
  37. RMap<String, Ur> ss = redissonClient.getMap("UR");
  38. Ur ur = ss.get(id);
  39. return ur.toString();
  40. }
  41. // 查询所有的 keys
  42. @GetMapping("/all")
  43. public String all(){
  44. RKeys keys = redissonClient.getKeys();
  45. Iterable<String> keys1 = keys.getKeys();
  46. keys1.forEach(System.out::println);
  47. return keys.toString();
  48. }
  49. // ================== ==============读写锁测试 =============================
  50. @GetMapping("/rw/set/{key}")
  51. public void rw_set(){
  52. // RedissonLock.
  53. RBucket<String> ls_count = redissonClient.getBucket("LS_COUNT");
  54. ls_count.set("300",360000000l, TimeUnit.SECONDS);
  55. }
  56. // 减法运算
  57. @GetMapping("/jf")
  58. public void jf(){
  59. String key = "S_COUNT";
  60. // RAtomicLong atomicLong = redissonClient.getAtomicLong(key);
  61. // atomicLong.set(sum);
  62. // long l = atomicLong.decrementAndGet();
  63. // System.out.println(l);
  64. RAtomicLong atomicLong = redissonClient.getAtomicLong(key);
  65. if (!atomicLong.isExists()) {
  66. atomicLong.set(300l);
  67. }
  68. while (i == 0) {
  69. if (atomicLong.get() > 0) {
  70. long l = atomicLong.getAndDecrement();
  71. try {
  72. Thread.sleep(1000l);
  73. } catch (InterruptedException e) {
  74. e.printStackTrace();
  75. }
  76. i --;
  77. System.out.println(Thread.currentThread().getName() + "->" + i + "->" + l);
  78. }
  79. }
  80. }
  81. @GetMapping("/rw/get")
  82. public String rw_get(){
  83. String key = "S_COUNT";
  84. Runnable r = new Runnable() {
  85. @Override
  86. public void run() {
  87. RAtomicLong atomicLong = redissonClient.getAtomicLong(key);
  88. if (!atomicLong.isExists()) {
  89. atomicLong.set(300l);
  90. }
  91. if (atomicLong.get() > 0) {
  92. long l = atomicLong.getAndDecrement();
  93. i --;
  94. System.out.println(Thread.currentThread().getName() + "->" + i + "->" + l);
  95. }
  96. }
  97. };
  98. while (i != 0) {
  99. new Thread(r).start();
  100. // new Thread(r).run();
  101. // new Thread(r).run();
  102. // new Thread(r).run();
  103. // new Thread(r).run();
  104. }
  105. RBucket<String> bucket = redissonClient.getBucket(key);
  106. String s = bucket.get();
  107. System.out.println("================线程已结束================================" + s);
  108. return s;
  109. }
  110. }

4.3 扩展

4.3.1 丰富的 jar 支持,尤其是对 Netty NIO 框架

4.3.2 丰富的配置机制选择,这里是详细的配置说明

关于序列化机制中,就有很多
image.png
image.png
更多序列化机制详见:https://github.com/redisson/redisson/wiki/4.-data-serialization

4.3.3 API 支持(部分展示),具体的 Redis —> RedissonClient

image.png

4.3.4 轻便的丰富的锁机制的实现

4.3.4.1 Lock

4.3.4.2 Fair Lock

4.3.4.3 MultiLock

4.3.4.4 RedLock

4.3.4.5 ReadWriteLock

4.3.4.6 Semaphore

4.3.4.7 PermitExpirableSemaphore

4.3.4.8 CountDownLatch

五、基于注解实现的 Redis 缓存

5.1 Maven 和 YML 配置

参考 RedisTemplate 配置
另外,还需要额外的配置类

  1. // todo 定义序列化,解决乱码问题
  2. @EnableCaching
  3. @Configuration
  4. @ConfigurationProperties(prefix = "spring.cache.redis")
  5. public class RedisCacheConfig {
  6. private Duration timeToLive = Duration.ZERO;
  7. public void setTimeToLive(Duration timeToLive) {
  8. this.timeToLive = timeToLive;
  9. }
  10. @Bean
  11. public CacheManager cacheManager(RedisConnectionFactory factory) {
  12. RedisSerializer<String> redisSerializer = new StringRedisSerializer();
  13. Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
  14. // 解决查询缓存转换异常的问题
  15. ObjectMapper om = new ObjectMapper();
  16. om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
  17. om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
  18. jackson2JsonRedisSerializer.setObjectMapper(om);
  19. // 配置序列化(解决乱码的问题)
  20. RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
  21. .entryTtl(timeToLive)
  22. .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
  23. .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
  24. .disableCachingNullValues();
  25. RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
  26. .cacheDefaults(config)
  27. .build();
  28. return cacheManager;
  29. }
  30. }

5.2 使用示例

  1. @Transactional
  2. @Service
  3. public class ReImpl implements RedisService {
  4. @Resource
  5. private CustomerRepo customerRepo;
  6. @Resource
  7. private StringRedisTemplate stringRedisTemplate;
  8. public static final String REDIS_CUSTOMERS_ONE = "Customers";
  9. public static final String REDIS_CUSTOMERS_ALL = "allList";
  10. // =====================================================================使用Spring cahce 注解方式实现缓存
  11. // ==================================单个操作
  12. @Override
  13. @Cacheable(value = "cache:customer", unless = "null == #result",key = "#id")
  14. public CustomersEntity cacheOne(Integer id) {
  15. final Optional<CustomersEntity> byId = customerRepo.findById(id);
  16. return byId.isPresent() ? byId.get() : null;
  17. }
  18. @Override
  19. @Cacheable(value = "cache:customer", unless = "null == #result", key = "#id")
  20. public CustomersEntity cacheOne2(Integer id) {
  21. final Optional<CustomersEntity> byId = customerRepo.findById(id);
  22. return byId.isPresent() ? byId.get() : null;
  23. }
  24. // todo 自定义redis缓存的key,
  25. @Override
  26. @Cacheable(value = "cache:customer", unless = "null == #result", key = "#root.methodName + '.' + #id")
  27. public CustomersEntity cacheOne3(Integer id) {
  28. final Optional<CustomersEntity> byId = customerRepo.findById(id);
  29. return byId.isPresent() ? byId.get() : null;
  30. }
  31. // todo 这里缓存到redis,还有响应页面是String(加了很多转义符\,),不是Json格式
  32. @Override
  33. @Cacheable(value = "cache:customer", unless = "null == #result", key = "#root.methodName + '.' + #id")
  34. public String cacheOne4(Integer id) {
  35. final Optional<CustomersEntity> byId = customerRepo.findById(id);
  36. return byId.map(JSONUtil::toJsonStr).orElse(null);
  37. }
  38. // todo 缓存json,不乱码已处理好,调整序列化和反序列化
  39. @Override
  40. @Cacheable(value = "cache:customer", unless = "null == #result", key = "#root.methodName + '.' + #id")
  41. public CustomersEntity cacheOne5(Integer id) {
  42. Optional<CustomersEntity> byId = customerRepo.findById(id);
  43. return byId.filter(obj -> !StrUtil.isBlankIfStr(obj)).orElse(null);
  44. }
  45. // ==================================删除缓存
  46. @Override
  47. @CacheEvict(value = "cache:customer", key = "'cacheOne5' + '.' + #id")
  48. public Object del(Integer id) {
  49. // 删除缓存后的逻辑
  50. return null;
  51. }
  52. @Override
  53. @CacheEvict(value = "cache:customer",allEntries = true)
  54. public void del() {
  55. }
  56. @CacheEvict(value = "cache:all",allEntries = true)
  57. public void delall() {
  58. }
  59. // ==================List操作
  60. @Override
  61. @Cacheable(value = "cache:all")
  62. public List<CustomersEntity> cacheList() {
  63. List<CustomersEntity> all = customerRepo.findAll();
  64. return all;
  65. }
  66. // todo 先查询缓存,再校验是否一致,然后更新操作,比较实用,要清楚缓存的数据格式(明确业务和缓存模型数据)
  67. @Override
  68. @CachePut(value = "cache:all",unless = "null == #result",key = "#root.methodName")
  69. public List<CustomersEntity> cacheList2() {
  70. List<CustomersEntity> all = customerRepo.findAll();
  71. return all;
  72. }
  73. }

5.3 扩展

基于 spring 缓存实现
image.png