关于后端:还在只用RedisTemplate访问Redis吗

44次阅读

共计 10455 个字符,预计需要花费 27 分钟才能阅读完成。

开始筹备
开始之前咱们须要有 Redis 装置,咱们采纳本机 Docker 运行 Redis, 次要命令如下

docker pull redis
docker run –name my_redis -d -p 6379:6379 redis
docker exec -it my_redis bash
redis-cli
复制代码
后面两个命令是启动 redis docker, 后两个是连贯到 docker, 在应用 redis-cli 去查看 redis 外面的内容,次要查看咱们存在 redis 外面的数据。

RedisTemplate
咱们先从 RedisTemplate 开始,这个是最好了解的一种形式,我之前在工作中也应用过这种形式,先看代码示例 咱们先定义一个 POJO 类

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Book implements Serializable {
   private Long id;
   private String name;

   private String author;
}
复制代码
一个很简略的 BOOK 类,三个字段:id,name 和 author. 再来一个 RedisTemplate 的 Bean

   @Bean
   public RedisTemplate<String, Book> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
       RedisTemplate<String, Book> template = new RedisTemplate<>();
       template.setConnectionFactory(redisConnectionFactory);
       return template;
  }
复制代码
再定义一个应用这个 RedisTemplate 的 Service 类

  public Optional<Book> findOneBook(String name) {
      HashOperations<String, String, Book> hashOperations = redisTemplate.opsForHash();
      if (redisTemplate.hasKey(CACHE) && hashOperations.hasKey(CACHE, name)) {
          log.info(“Get book {} from Redis.”, name);
          return Optional.of(hashOperations.get(CACHE, name));
      }

      Optional<Book> book = bookRepository.getBook(name);
      log.info(“Book Found: {}”, book);
      if (book.isPresent()) {
          log.info(“Put book {} to Redis.”, name);
          hashOperations.put(CACHE, name, book.get());
          redisTemplate.expire(CACHE, 10, TimeUnit.MINUTES);
      }
      return book;
  }
复制代码
咱们应用 Hash 来存储这个 Book 信息,在下面的办法中查找书名存不存在 Redis 中,如果存在就间接返回,如果不存在就去长久化存储中找,找到就再通过 Template 写入到 Redis 中,这是缓存的通用做法。应用起来感觉很不便。咱们这里为了简略没有应用长久化存储,就硬编码了几条数据, 代码如下

@Repository
public class BookRepository {
   Map<String, Book> bookMap = new HashMap<>();
   public BookRepository(){
       bookMap.put(“apache kafka”, Book.builder()
              .name(“apache kafka”).id(1L).author(“zhangsan”)
              .build());
       bookMap.put(“python”, Book.builder()
              .name(“python”).id(2L).author(“lisi”)
              .build());
  }

   public Optional<Book> getBook(String name){
       if(bookMap.containsKey(name)){
           return Optional.of(bookMap.get(name));
      }
       else{
           return Optional.empty();
      }
  }
}
复制代码
咱们调用 bookService.findOneBook(“python”)和 bookService.findOneBook(“apache kafka”); 来把数据写入到换存中

咱们来看下存储在 Redis 的数据长什么样子。

127.0.0.1:6379> keys *
1) “\xac\xed\x00\x05t\x00\x04book”
127.0.0.1:6379> type “\xac\xed\x00\x05t\x00\x04book”
hash
127.0.0.1:6379> hgetall “\xac\xed\x00\x05t\x00\x04book”
1) “\xac\xed\x00\x05t\x00\x06python”
2) “\xac\xed\x00\x05sr\x00&com.ken.redistemplatesample.model.Book=\x19\x96\xfb\x7f\x7f\xda\xbe\x02\x00\x03L\x00\x06authort\x00\x12Ljava/lang/String;L\x00\x02idt\x00\x10Ljava/lang/Long;L\x00\x04nameq\x00~\x00\x01xpt\x00\x04lisisr\x00\x0ejava.lang.Long;\x8b\xe4\x90\xcc\x8f#\xdf\x02\x00\x01J\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\x00\x00\x00\x00\x00\x02t\x00\x06python”
3) “\xac\xed\x00\x05t\x00\x0capache kafka”
4) “\xac\xed\x00\x05sr\x00&com.ken.redistemplatesample.model.Book=\x19\x96\xfb\x7f\x7f\xda\xbe\x02\x00\x03L\x00\x06authort\x00\x12Ljava/lang/String;L\x00\x02idt\x00\x10Ljava/lang/Long;L\x00\x04nameq\x00~\x00\x01xpt\x00\bzhangsansr\x00\x0ejava.lang.Long;\x8b\xe4\x90\xcc\x8f#\xdf\x02\x00\x01J\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\x00\x00\x00\x00\x00\x01t\x00\x0capache kafka”
复制代码
咱们能够看到数据被存在了 key 是“\xac\xed\x00\x05t\x00\x04book”的一个 Hash 表中,Hash 外面有两条记录。大家发现一个问题没有?就是这个 key 不是咱们设想的用“book”做 key, 而是多了一串 16 进制的码,这是因为 RedisTemplate 应用了默认的 JdkSerializationRedisSerializer 去序列化咱们的 key 和 value, 如果大家都用 Java 语言那没有问题,如果有人用 Java 语言写,有人用别的语言读,那就有问题,就像我开始的时候用 hgetall “book” 始终拿不到数据那样。RedisTemplate 也提供了 StringRedisTemplate 来不便大家须要应用 String 来序列化 redis 外面的数据。简略看下代码

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

   public Optional<String> getBookString(String name){
       HashOperations<String, String, String> hashOperations = stringRedisTemplate.opsForHash();
       if (stringRedisTemplate.hasKey(STRINGCACHE) && hashOperations.hasKey(STRINGCACHE, name)) {
           log.info(“Get book {} from Redis.”, name);
           return Optional.of(hashOperations.get(STRINGCACHE, name));
      }

       Optional<Book> book = bookRepository.getBook(name);
       log.info(“Book Found: {}”, book);
       if (book.isPresent()) {
           log.info(“Put book {} to Redis.”, name);
           hashOperations.put(STRINGCACHE, name, book.get().getAuthor());
           stringRedisTemplate.expire(STRINGCACHE, 10, TimeUnit.MINUTES);
           return Optional.of(book.get().getAuthor());
      }
       return Optional.empty();
  }
复制代码
应用上就没有那么不便,你就得本人写须要存的是哪个字段,读出来是哪个字段。

127.0.0.1:6379> keys *
1) “string_book”
127.0.0.1:6379> hgetall string_book
1) “python”
2) “lisi”
3) “apache kafka”
4) “zhangsan”
复制代码
如上图所示,应用客户端读出来看起来就比拟清新一些。也能够看到占用的 Size 会小很多,咱们这个例子相差 7 倍,如果是数据量大,这个还是比拟大的节约。

127.0.0.1:6379> keys *
1) “\xac\xed\x00\x05t\x00\x04book”
2) “string_book”
127.0.0.1:6379> memory usage “string_book”
(integer) 104
127.0.0.1:6379> memory usage “\xac\xed\x00\x05t\x00\x04book”
(integer) 712
复制代码
JPA Repository
咱们晓得应用 JPA Repository 来拜访 DataBase 的时候,增删改查那样的操作可能很不便的实现,根本就是定义个接口,代码都不必写,Spring 就帮咱们实现了大部分的工作,那么拜访 Redis 是不是也能够这样呢?答案是必定的,咱们来看代码 首先咱们还是定义一个 POJO

@RedisHash(value = “cache-book”, timeToLive = 600)
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class CacheBook implements Serializable {

   @Id
   private Long userId;

   @Indexed
   private String name;

   private String author;
}
复制代码
这个类与咱们下面 template 下面的类的区别就是咱们加了两个注解,在类结尾加了 @RedisHash(value = “cache-book”, timeToLive = 600) 在字段下面加了 @Id 和 @Indexed 定义一个 Repository 的接口

public interface CacheBookRepository extends CrudRepository<CacheBook, Long> {
   Optional<CacheBook> findOneByName(String name);
}
复制代码
再定义一个 service 和下面那个例子 template 一样,缓存中有就到缓存中拿,没有就到长久化存储中找,并写入缓存

@Slf4j
@Service
public class BookService {
   private static final String CACHE = “repository-book”;
   @Autowired
   private CacheBookRepository cacheRepository;

   @Autowired
   private BookRepository bookRepository;

   public Optional<CacheBook> findOneBook(String name) {
       Optional<CacheBook> optionalCacheBook = cacheRepository.findOneByName(name);
       if(!optionalCacheBook.isPresent())
       {
           Optional<CacheBook> book = bookRepository.getBook(name);
           log.info(“Book Found: {}”, book);
           if (book.isPresent()) {
               log.info(“Put book {} to Redis.”, name);
               cacheRepository.save(book.get());
           }
           return book;
       }
       return optionalCacheBook;
   }
}
复制代码
代码很简略,简略到不敢相信是真的。还是一样,调用这个办法,咱们来看存在 Redis 外面的数据

127.0.0.1:6379> keys *
1) “repository-book:2”
2) “repository-book:2:idx”
3) “repository-book”
4) “repository-book:name:apache kafka”
5) “repository-book:name:python”
6) “repository-book:1:idx”
7) “repository-book:1”
复制代码
哇,感觉存的内容有些多,不必怕咱们来看下各自存什么数据 首先看最短的一个

127.0.0.1:6379> smembers repository-book
1) “1”
2) “2”
复制代码
它外面存的是咱们的 id 所有的 value, 能够用来判断 id 是否存在 再来看

127.0.0.1:6379> hgetall repository-book:2
1) “_class”
2) “com.ken.redisrepositorysample.model.CacheBook”
3) “author”
4) “lisi”
5) “name”
6) “python”
7) “userId”
8) “2”
复制代码
这个是咱们数据寄存的中央

127.0.0.1:6379> smembers repository-book:1:idx
1) “repository-book:name:apache kafka”
127.0.0.1:6379> smembers “repository-book:name:apache kafka”
1) “1”
复制代码
另外两个都是 set,存在在它们外面的数据是索引信息。由此能够看出通过 JPA Repository 的形式,代码很少,而且存储的数据也很通用,集体感觉是比拟现实的拜访办法。

Cache
咱们曾经看了两种形式,在拜访的时候遵循这样的模式:缓存中有就从缓存中返回,没有就从长久化存储中找,而后写入缓存,这部分代码我也不想本人写,那么 Cache 就是你的救星。咱们先看代码 咱们这次应用内存数据库 H2 作为长久化存储,放一个 schema.sql 在 resouces 上面

drop table t_book if exists;


create table t_book (
  id bigint auto_increment,
  create_time timestamp,
  update_time timestamp,
  name varchar(255),
  author varchar(200),
   primary key (id)
);

insert into t_book (name, author, create_time, update_time) values (‘python’, ‘zhangsan’, now(), now());
insert into t_book (name, author, create_time, update_time) values (‘hadoop’, ‘lisi’, now(), now());
insert into t_book (name, author, create_time, update_time) values (‘java’, ‘wangwu’, now(), now());
复制代码
而后定义 POJO

@Entity
@Table(name = “T_BOOK”)
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class CacheBook implements Serializable {

   @Id
   @GeneratedValue(strategy = GenerationType.IDENTITY)
   private Long id;

   private String name;

   private String author;

   @Column(updatable = false)
   @CreationTimestamp
   private Date createTime;
   @UpdateTimestamp
   private Date updateTime;
}
复制代码
齐全是和数据库绑定的代码,和缓存没有任何关系 一个 Repository 来拜访数据库

public interface BookRepository extends JpaRepository<CacheBook, Long> {
}
复制代码
定义一个 service 来调用它

@Slf4j
@Service
@CacheConfig(cacheNames = “cache-book”)
public class BookService {

   @Autowired
   private BookRepository bookRepository;

   @Cacheable
   public List<CacheBook> findAllCoffee() {
       return bookRepository.findAll();
  }

   @CacheEvict
   public void reloadCoffee() {
  }
}
复制代码
这里就比拟要害了,在类上加上了注解 @CacheConfig(cacheNames = “cache-book”) 在办法下面加上了 Cacheable 和 CacheEvict,Cacheable 这个办法就是用来实现逻辑,有就从缓存中拿,没有就从数据库拿的,CacheEvict 是调用这个办法的时候革除缓存。而后再启动入口程序的中央加上注解 @EnableJpaRepositories @EnableCaching(proxyTargetClass = true) 在配置文件 application.properties 中加上

spring.jpa.hibernate.ddl-auto=none
spring.jpa.properties.hibernate.show_sql=true
spring.jpa.properties.hibernate.format_sql=true

management.endpoints.web.exposure.include=*

spring.cache.type=redis
spring.cache.cache-names=cache-book
spring.cache.redis.time-to-live=600000
spring.cache.redis.cache-null-values=false

spring.redis.host=localhost
复制代码
这样就能够了,感觉就是通过配置下就把缓存给实现了,十分的简略 咱们来看 Redis 中是怎么存的

127.0.0.1:6379> keys *
1) “cache-book::SimpleKey []”
127.0.0.1:6379> get “cache-book::SimpleKey []”
“\xac\xed\x00\x05sr\x00\x13java.util.ArrayListx\x81\xd2\x1d\x99\xc7a\x9d\x03\x00\x01I\x00\x04sizexp\x00\x00\x00\x03w\x04\x00\x00\x00\x03sr\x00(com.ken.rediscachesample.model.CacheBook\xec\xcbR=\xe1U\x9b\xf7\x02\x00\x05L\x00\x06authort\x00\x12Ljava/lang/String;L\x00\ncreateTimet\x00\x10Ljava/util/Date;L\x00\x02idt\x00\x10Ljava/lang/Long;L\x00\x04nameq\x00~\x00\x03L\x00\nupdateTimeq\x00~\x00\x04xpt\x00\bzhangsansr\x00\x12java.sql.Timestamp&\x18\xd5\xc8\x01S\xbfe\x02\x00\x01I\x00\x05nanosxr\x00\x0ejava.util.Datehj\x81\x01KYt\x19\x03\x00\x00xpw\b\x00\x00\x01\x84\xff]\x85\xb0x\b-\x81\x80sr\x00\x0ejava.lang.Long;\x8b\xe4\x90\xcc\x8f#\xdf\x02\x00\x01J\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\x00\x00\x00\x00\x00\x01t\x00\x06pythonsq\x00~\x00\bw\b\x00\x00\x01\x84\xff]\x85\xb0x\b-\x81\x80sq\x00~\x00\x02t\x00\x04lisisq\x00~\x00\bw\b\x00\x00\x01\x84\xff]\x85\xb0x\b<\xbf\xd8sq\x00~\x00\x0b\x00\x00\x00\x00\x00\x00\x00\x02t\x00\x06hadoopsq\x00~\x00\bw\b\x00\x00\x01\x84\xff]\x85\xb0x\b<\xbf\xd8sq\x00~\x00\x02t\x00\x06wangwusq\x00~\x00\bw\b\x00\x00\x01\x84\xff]\x85\xb0x\b<\xbf\xd8sq\x00~\x00\x0b\x00\x00\x00\x00\x00\x00\x00\x03t\x00\x04javasq\x00~\x00\bw\b\x00\x00\x01\x84\xff]\x85\xb0x\b<\xbf\xd8x”
复制代码
看到没有,就是当成 Redis 外面的 String 来存的,如果数据量比拟小,那是十分的不便,如果数据量大,这种形式就有些问题了。

总结
咱们看了这三种形式,这里仅仅是做了个入门,每个外面都有很多细节的中央须要去钻研和应用,整体的感觉是要想应用的简略,那么存储在 Redis 中的数据就要量少,量大后,就须要本人来定制了,那基本上要用 RedisTemplate 来做一些工作。

正文完
 0