艾瑞
艾瑞
新闻资讯
中心动态 学习技巧
Spring Boot + Redis 实现各种操作,写得真好!
2021-11-15

一、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 已声明)

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


application-dev.yml

spring:
 redis:
   host: 192.168.1.140
   port: 6379
   password:
   database: 15 # 指定redis的分库(共16015

3.2、使用示例

 @Resource
private StringRedisTemplate stringRedisTemplate;

   @Override
   public CustomersEntity findById(Integer id) {
       // 需要缓存
       // 所有涉及的缓存都需要删除,或者更新
       try {
           String toString = stringRedisTemplate.opsForHash().get(REDIS_CUSTOMERS_ONE, id + "").toString();
           if (toString != null) {
               return JSONUtil.toBean(toString, CustomersEntity.class);
           }
       } catch (Exception e) {
           e.printStackTrace();
       }
       // 缓存为空的时候,先查,然后缓存redis
       Optional<CustomersEntity> byId = customerRepo.findById(id);
       if (byId.isPresent()) {
           CustomersEntity customersEntity = byId.get();
           try {
               stringRedisTemplate.opsForHash().put(REDIS_CUSTOMERS_ONE, id + "", JSONUtil.toJsonStr(customersEntity));
           } catch (Exception e) {
               e.printStackTrace();
           }
           return customersEntity;
       }
       return null;
   }

3.3、扩展

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

Spring Boot + Redis 实现各种操作,写得真好!(图1)

3.3.2、stringRedisTemplate API(部分展示)

opsForHash --> hash 操作
opsForList --> list 操作
opsForSet --> set 操作
opsForValue --> string 操作
opsForZSet --> Zset 操作

1-211115091553396.jpg

3.3.3 StringRedisTemplate 默认序列化机制

public class StringRedisTemplate extends RedisTemplate<String, String> {

/**
* Constructs a new <code>StringRedisTemplate</code> instance. {@link #setConnectionFactory(RedisConnectionFactory)}
* and {@link #afterPropertiesSet()} still need to be called.
*/

public StringRedisTemplate() {
RedisSerializer<String> stringSerializer = new StringRedisSerializer();
setKeySerializer(stringSerializer);
setValueSerializer(stringSerializer);
setHashKeySerializer(stringSerializer);
setHashValueSerializer(stringSerializer);
}
}

四、RedissonClient 操作示例

4.1 基本配置

4.1.1、Maven pom 引入

<dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-data-redis</artifactId>
       </dependency>
       <dependency>
           <groupId>org.redisson</groupId>
           <artifactId>redisson</artifactId>
           <version>3.8.2</version>
           <optional>true</optional>
       </dependency>
       <dependency>
           <groupId>org.redisson</groupId>
           <artifactId>redisson-spring-boot-starter</artifactId>
           <version>LATEST</version>
       </dependency>

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

redisson-config.yml

# Redisson 配置
singleServerConfig:
 address: "redis://192.168.1.140:6379"
 password: null
 clientName: null
 database: 15 #选择使用哪个数据库0~15
 idleConnectionTimeout: 10000
 pingTimeout: 1000
 connectTimeout: 10000
 timeout: 3000
 retryAttempts: 3
 retryInterval: 1500
 reconnectionTimeout: 3000
 failedAttempts: 3
 subscriptionsPerConnection: 5
 subscriptionConnectionMinimumIdleSize: 1
 subscriptionConnectionPoolSize: 50
 connectionMinimumIdleSize: 32
 connectionPoolSize: 64
 dnsMonitoringInterval: 5000
 #dnsMonitoring: false

threads: 0
nettyThreads: 0
codec:
 class: "org.redisson.codec.JsonJacksonCodec"
transportMode: "NIO"


或者,配置 redisson-config.json

{
 "singleServerConfig": {
   "idleConnectionTimeout": 10000,
   "pingTimeout": 1000,
   "connectTimeout": 10000,
   "timeout": 3000,
   "retryAttempts": 3,
   "retryInterval": 1500,
   "reconnectionTimeout": 3000,
   "failedAttempts": 3,
   "password": null,
   "subscriptionsPerConnection": 5,
   "clientName": null,
   "address": "redis://192.168.1.140:6379",
   "subscriptionConnectionMinimumIdleSize": 1,
   "subscriptionConnectionPoolSize": 50,
   "connectionMinimumIdleSize": 10,
   "connectionPoolSize": 64,
   "database": 0,
   "dnsMonitoring": false,
   "dnsMonitoringInterval": 5000
 },
 "threads": 0,
 "nettyThreads": 0,
 "codec": null,
 "useLinuxNativeEpoll": false
}


4.1.3、读取配置

新建读取配置类

@Configuration
public class RedissonConfig {

   @Bean
   public RedissonClient redisson() throws IOException {

       // 两种读取方式,Config.fromYAML 和 Config.fromJSON
//        Config config = Config.fromJSON(RedissonConfig.class.getClassLoader().getResource("redisson-config.json"));
       Config config = Config.fromYAML(RedissonConfig.class.getClassLoader().getResource("redisson-config.yml"));
       return Redisson.create(config);
   }
}

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

spring:
 redis:
   redisson:
     config: classpath:redisson-config.yaml

4.2 使用示例

@RestController
@RequestMapping("/")
public class TeController {

   @Autowired
   private RedissonClient redissonClient;

   static long i = 20;
   static long sum = 300;

//    ========================== String =======================
   @GetMapping("/set/{key}")
   public String s1(@PathVariable String key) {
       // 设置字符串
       RBucket<String> keyObj = redissonClient.getBucket(key);
       keyObj.set(key + "1-v1");
       return key;
   }

   @GetMapping("/get/{key}")
   public String g1(@PathVariable String key) {
       // 设置字符串
       RBucket<String> keyObj = redissonClient.getBucket(key);
       String s = keyObj.get();
       return s;
   }

   //    ========================== hash =======================-=

   @GetMapping("/hset/{key}")
   public String h1(@PathVariable String key) {

       Ur ur = new Ur();
       ur.setId(MathUtil.randomLong(1,20));
       ur.setName(key);
     // 存放 Hash
       RMap<String, Ur> ss = redissonClient.getMap("UR");
       ss.put(ur.getId().toString(), ur);
       return ur.toString();
   }

   @GetMapping("/hget/{id}")
   public String h2(@PathVariable String id) {
       // hash 查询
       RMap<String, Ur> ss = redissonClient.getMap("UR");
       Ur ur = ss.get(id);
       return ur.toString();
   }

   // 查询所有的 keys
   @GetMapping("/all")
   public String all(){
       RKeys keys = redissonClient.getKeys();
       Iterable<String> keys1 = keys.getKeys();
       keys1.forEach(System.out::println);
       return keys.toString();
   }

   // ================== ==============读写锁测试 =============================

   @GetMapping("/rw/set/{key}")
   public void rw_set(){
//        RedissonLock.
       RBucket<String> ls_count = redissonClient.getBucket("LS_COUNT");
       ls_count.set("300",360000000l, TimeUnit.SECONDS);
   }

   // 减法运算
   @GetMapping("/jf")
   public void jf(){

       String key = "S_COUNT";

//        RAtomicLong atomicLong = redissonClient.getAtomicLong(key);
//        atomicLong.set(sum);
//        long l = atomicLong.decrementAndGet();
//        System.out.println(l);

       RAtomicLong atomicLong = redissonClient.getAtomicLong(key);
       if (!atomicLong.isExists()) {
           atomicLong.set(300l);
       }

       while (i == 0) {
           if (atomicLong.get() > 0) {
               long l = atomicLong.getAndDecrement();
                       try {
                           Thread.sleep(1000l);
                       } catch (InterruptedException e) {
                           e.printStackTrace();
                       }
               i --;
               System.out.println(Thread.currentThread().getName() + "->" + i + "->" + l);
           }
       }


   }

   @GetMapping("/rw/get")
   public String rw_get(){

       String key = "S_COUNT";
       Runnable r = new Runnable() {
           @Override
           public void run() {
               RAtomicLong atomicLong = redissonClient.getAtomicLong(key);
               if (!atomicLong.isExists()) {
                   atomicLong.set(300l);
               }
               if (atomicLong.get() > 0) {
                   long l = atomicLong.getAndDecrement();
                   i --;
                   System.out.println(Thread.currentThread().getName() + "->" + i + "->" + l);
               }
           }
       };

       while (i != 0) {
           new Thread(r).start();
//            new Thread(r).run();
//            new Thread(r).run();
//            new Thread(r).run();
//            new Thread(r).run();
       }


       RBucket<String> bucket = redissonClient.getBucket(key);
       String s = bucket.get();
       System.out.println("================线程已结束================================" + s);

       return s;
   }

}


4.3 扩展

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

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

关于序列化机制中,就有很多

Spring Boot + Redis 实现各种操作,写得真好!(图3)

1-2111150916303X.jpg

4.3.3 API 支持(部分展示),具体的 Redis --> RedissonClient , 可查看这里

1-211115091645924.jpg

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 配置

另外,还需要额外的配置类

// todo 定义序列化,解决乱码问题
@EnableCaching
@Configuration
@ConfigurationProperties(prefix = "spring.cache.redis")
public class RedisCacheConfig {

   private Duration timeToLive = Duration.ZERO;

   public void setTimeToLive(Duration timeToLive) {
       this.timeToLive = timeToLive;
   }

   @Bean
   public CacheManager cacheManager(RedisConnectionFactory factory) {
       RedisSerializer<String> redisSerializer = new StringRedisSerializer();
       Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);

       // 解决查询缓存转换异常的问题
       ObjectMapper om = new ObjectMapper();
       om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
       om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
       jackson2JsonRedisSerializer.setObjectMapper(om);

       // 配置序列化(解决乱码的问题)
       RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
               .entryTtl(timeToLive)
               .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
               .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
               .disableCachingNullValues();

       RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
               .cacheDefaults(config)
               .build();
       return cacheManager;
   }

}

5.2 使用示例

@Transactional
@Service
public class ReImpl implements RedisService {

   @Resource
   private CustomerRepo customerRepo;
   @Resource
   private StringRedisTemplate stringRedisTemplate;

   public static final String REDIS_CUSTOMERS_ONE = "Customers";

   public static final String REDIS_CUSTOMERS_ALL = "allList";

   // =====================================================================使用Spring cahce 注解方式实现缓存
   // ==================================单个操作

   @Override
   @Cacheable(value = "cache:customer", unless = "null == #result",key = "#id")
   public CustomersEntity cacheOne(Integer id) {
       final Optional<CustomersEntity> byId = customerRepo.findById(id);
       return byId.isPresent() ? byId.get() : null;
   }

   @Override
   @Cacheable(value = "cache:customer", unless = "null == #result", key = "#id")
   public CustomersEntity cacheOne2(Integer id) {
       final Optional<CustomersEntity> byId = customerRepo.findById(id);
       return byId.isPresent() ? byId.get() : null;
   }

    // todo 自定义redis缓存的key,
   @Override
   @Cacheable(value = "cache:customer", unless = "null == #result", key = "#root.methodName + '.' + #id")
   public CustomersEntity cacheOne3(Integer id) {
       final Optional<CustomersEntity> byId = customerRepo.findById(id);
       return byId.isPresent() ? byId.get() : null;
   }

   // todo 这里缓存到redis,还有响应页面是String(加了很多转义符\,),不是Json格式
   @Override
   @Cacheable(value = "cache:customer", unless = "null == #result", key = "#root.methodName + '.' + #id")
   public String cacheOne4(Integer id) {
       final Optional<CustomersEntity> byId = customerRepo.findById(id);
       return byId.map(JSONUtil::toJsonStr).orElse(null);
   }

    // todo 缓存json,不乱码已处理好,调整序列化和反序列化
   @Override
   @Cacheable(value = "cache:customer", unless = "null == #result", key = "#root.methodName + '.' + #id")
   public CustomersEntity cacheOne5(Integer id) {
       Optional<CustomersEntity> byId = customerRepo.findById(id);
       return byId.filter(obj -> !StrUtil.isBlankIfStr(obj)).orElse(null);
   }



   // ==================================删除缓存
   @Override
   @CacheEvict(value = "cache:customer", key = "'cacheOne5' + '.' + #id")
   public Object del(Integer id) {
       // 删除缓存后的逻辑
       return null;
   }

   @Override
   @CacheEvict(value = "cache:customer",allEntries = true)
   public void del() {

   }

   @CacheEvict(value = "cache:all",allEntries = true)
   public void delall() {

   }
   // ==================List操作

   @Override
   @Cacheable(value = "cache:all")
   public List<CustomersEntity> cacheList() {
       List<CustomersEntity> all = customerRepo.findAll();
       return all;
   }

   // todo 先查询缓存,再校验是否一致,然后更新操作,比较实用,要清楚缓存的数据格式(明确业务和缓存模型数据)
   @Override
   @CachePut(value = "cache:all",unless = "null == #result",key = "#root.methodName")
   public List<CustomersEntity> cacheList2() {
       List<CustomersEntity> all = customerRepo.findAll();
       return all;
   }

}



5.3 扩展

基于 spring 缓存实现

1-211115091F2152.jpg