8.4.1 使用Redis来缓存查找

现在先从设置许可证服务以使用Redis开始。幸运的是,Spring Data已经简化了将Redis引入许可证服务中的工作。要在许可证服务中使用Redis,需要做以下4件事情。
    (1)配置许可证服务以包含Spring Data Redis依赖项。
    (2)构造一个到Redis服务器的数据库连接。
    (3)定义Spring Data Redis存储库,代码将使用它与一个Redis散列进行交互。
    (4)使用Redis和许可证服务来存储和读取组织数据。
    
1.配置许可证服务以包含Spring Data Redis依赖项
    需要做的第一件事就是将spring-data-redis、jedis以及common-pools2依赖项包含在许可证服务的pom.xml文件中。代码清单8-7展示了要包含的依赖项。
    
代码清单8-7 添加Spring Redis依赖项
<dependency>   
<groupId>org.springframework.data</groupId>   
<artifactId>spring-data-redis</artifactId>   
<version>1.7.4.RELEASE</version> 
</dependency>
<dependency>   
<groupId>redis.clients</groupId>   
<artifactId>jedis</artifactId>  
 <version>2.9.0</version> 
</dependency> 
<dependency>   
<groupId>org.apache.commons</groupId>   
<artifactId>commons-pool2</artifactId>  
 <version>2.0</version> 
</dependency>
   
 2.构造一个到Redis服务器的数据库连接
    既然已经在Maven中添加了依赖项,接下来就需要建立一个到Redis服务器的连接。Spring使用开源项目Jedis与Redis服务器进行通信。要与特定的Redis实例进行通信,需要在licensing-service/src/main/java/com/thoughtmechanix/licenses/Application.java中的Application类中公开一个JedisConnectionFactory作为Spring bean。一旦连接到Redis,将使用该连接创建一个Spring RedisTemplate对象。我们很快会实现Spring Data存储库类,它们将使用RedisTemplate对象来执行查询,并将组织服务数据保存到Redis服务中。代码清单8-8展示了这段代码。
    
代码清单8-8 确定许可证服务将如何与Redis进行通信
 package com.thoughtmechanix.licenses;
// 为了简洁,省略了大部分import语句
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker
@EnableBinding(Sink.class)
public class Application {
    @Autowired
    private ServiceConfig serviceConfig;
    ⇽--- jedisConnectionFactory()方法设置到Redis服务器的实际数据库连接     
    @Bean
    public JedisConnectionFactory jedisConnectionFactory() {
        JedisConnectionFactory jedisConnFactory = new JedisConnectionFactory();
        jedisConnFactory.setHostName( serviceConfig.getRedisServer());
        jedisConnFactory.setPort( serviceConfig.getRedisPort() );
        return jedisConnFactory;
    }
    ⇽--- redisTemplate()方法创建一个RedisTemplate,用于对Redis服务器执行操作         
    @Bean
    public RedisTemplate<String, Object> redisTemplate() {
        RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
        template.setConnectionFactory(jedisConnectionFactory());
        return template;
    }
}    
    
建立许可证服务与Redis进行通信的基础工作已经完成。现在让我们来编写从Redis查询、添加、更新和删除数据的逻辑。
    
3.定义Spring Data Redis存储库
Redis是一个键值数据存储,它的作用类似于一个大的、分布式的、内存中的HashMap。在最简单的情况下,它存储数据并按键查找数据。Redis没有任何复杂的查询语言来检索数据。它的简单性是它的优点,也是这么多项目采用它的原因之一。
    因为我们使用Spring Data来访问Redis存储,所以需要定义一个存储库类。读者可能还记得在第2章中,Spring Data使用用户定义的存储库类为Java类提供一个简单的机制来访问Postgres数据库,而无须开发人员编写低级的SQL查询。
    对于许可证服务,我们将为Redis存储库定义两个文件。将要编写的第一个文件是一个Java接口,它将被注入任何需要访问Redis的许可证服务类中。这个OrganizationRedisRepository接口(在licensing- service/src/main/java/com/thoughtmechanix/licenses/repository/OrganizationRedisRepository.java中)如代码清单8-9所示。
    
代码清单8-9 OrganizationRedisRepository定义用于调用Redis的方法
package com.thoughtmechanix.licenses.repository;
public interface OrganizationRedisRepository {
    void saveOrganization(Organization org);
    void updateOrganization(Organization org);
    void deleteOrganization(String organizationId);
    Organization findOrganization(String organizationId);
}
 
第二个文件是OrganizationRedisRepository接口的实现。这个接口的实现,即licensing-service/src/main/java/com/thoughtmechanix/licenses/repository/OrganizationRedisRepositoryImpl.java中的OranizationRedisRepositoryImpl类,使用了之前在代码清单8-8中定义的RedisTemplate来与Redis服务器进行交互,并对Redis服务器执行操作。代码清单8-10展示了所使用的代码。
    
代码清单8-10 OrganizationRedisRepositoryImpl实现
package com.thoughtmechanix.licenses.repository;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Repository;
 
import javax.annotation.PostConstruct;
⇽--- 这个@Repository注解告诉Spring,这个类是一个与Spring Data一起使用的存储库类
@Repository
public class OrganizationRedisRepositoryImpl implements OrganizationRedisRepository {
    ⇽--- 在Redis服务器中存储组织数据的散列的名称
    private static final String HASH_NAME ="organization";
 
    private RedisTemplate<String, Organization> redisTemplate;
    ⇽--- HashOperations类包含一组用于在Redis服务器上执行数据操作的辅助方法
    private HashOperations hashOperations;
 
    public OrganizationRedisRepositoryImpl(){
        super();
    }
 
    @Autowired
    private OrganizationRedisRepositoryImpl(RedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }
 
    @PostConstruct
    private void init() {
        hashOperations = redisTemplate.opsForHash();
    }
 
    
    @Override
    public void saveOrganization(Organization org) {
        ⇽--- 与Redis的所有交互都将使用由键存储的单个Organization对象
        hashOperations.put(HASH_NAME, org.getId(), org);
    }
 
    @Override
    public void updateOrganization(Organization org) {
        hashOperations.put(HASH_NAME, org.getId(), org);
    }
 
    @Override
    public void deleteOrganization(String organizationId) {
        hashOperations.delete(HASH_NAME, organizationId);
    }
 
    @Override
    public Organization findOrganization(String organizationId) {
       return (Organization) hashOperations.get(HASH_NAME, organizationId);
    }
}
OrganizationRedisRepositoryImpl包含用于从Redis存储和检索数据的所有CRUD(Create、Read、Update和Delete)逻辑。在代码清单8-10所示的代码中有两个关键问题需要注意。
    
Redis中的所有数据都是通过一个键存储和检索的。因为是存储从组织服务中检索到的数据,所以自然选择组织ID作为存储组织记录的键。
    一个Redis服务器可以包含多个散列和数据结构。在针对Redis服务器的每个操作中,需要告诉Redis执行操作的数据结构的名字。在代码清单8-10中,使用的数据结构名称存储在HASH_NAME常量中,其值为organization”。
   
 4.使用Redis和许可证服务来存储和读取组织数据
    在完成对Redis执行操作的代码之后,就可以修改许可证服务,以便每次许可证服务需要组织数据时,它会在调用组织服务之前检查Redis缓存。检查Redis的逻辑将出现在licensing- service/src/main/java/com/thoughtmechanix/licenses/clients/OrganizationRestTemplateClient.java中的OrganizationRestTemplateClient类中。这个类的代码如代码清单8-11所示。
   
 
代码清单8-11 OrganizationRestTemplateClient将实现缓存逻辑
@Component
public class OrganizationRestTemplateClient {
    @Autowired
    RestTemplate restTemplate;
    ⇽--- OrganizationRedisRepository被自动装配到OrganizationRestTemplateClient     
    @Autowired
    OrganizationRedisRepository orgRedisRepo;
 
    private static final Logger logger = LoggerFactory.getLogger(OrganizationRestTemplateClient.class);
    ⇽--- 尝试使用组织ID从Redis中检索Organization类
    private Organization checkRedisCache(String organizationId) {
        try {
            return orgRedisRepo.findOrganization(organizationId);
        }
        catch (Exception ex){
            logger.error("Error encountered while trying to retrieve organization {} check Redis Cache.  Exception {}", organizationId, ex);
            return null;
        }
    }
 
    private void cacheOrganizationObject(Organization org) {
        try {
            orgRedisRepo.saveOrganization(org);
        }catch (Exception ex){
            logger.error("Unable to cache organization {} in Redis. Exception {}", org.getId(), ex);
        }
    }
 
    public Organization getOrganization(String organizationId){
        logger.debug("In Licensing Service.getOrganization: {}", UserContext.getCorrelationId());
 
        Organization org = checkRedisCache(organizationId);
         ⇽--- 如果无法从Redis中检索出数据,那么将调用组织服务从源数据库检索数据
        if (org!=null){
            logger.debug("I have successfully retrieved an organization {} from the redis cache: {}", organizationId, org);
            return org;
        }
 
        logger.debug("Unable to locate organization from the redis cache: {}.", organizationId);
 
        ResponseEntity<Organization> restExchange =
                restTemplate.exchange(
                        "http://zuulservice/api/organization/v1/organizations/{organizationId}",
                        HttpMethod.GET,
                        null, Organization.class, organizationId);
 
        /*将记录保存到缓存中*/  
        org = restExchange.getBody();
        ⇽--- 将检索到的对象保存到缓存中     
        if (org!=null) {
            cacheOrganizationObject(org);
        }
 
        return org;
    }
}
getOrganization()方法是调用组织服务的地方。在进行实际的REST调用之前,尝试使用checkRedisCache()方法从Redis中检索与调用相关联的组织对象。如果该组织对象不在Redis中,则代码将返回一个null值。如果从checkRedisCache()方法返回一个null值,那么代码将调用组织服务的REST端点来检索所需的组织记录。如果组织服务返回一条组织记录,那么将使用cacheOrganizationObject()方法缓存返回的组织对象。
    注意
    
    在与缓存进行交互时,要特别注意异常处理。为了提高弹性,如果无法与Redis服务器通信,我们绝对不会让整个调用失败。相反,我们会记录异常,并让调用转到组织服务。在这个特定的用例中,缓存旨在帮助提高性能,而缓存服务器的缺失不应该影响调用的成功。
    有了Redis缓存代码,接下来应该访问许可证服务(是的,目前只有两个服务,但是有很多基础设施),并查看代码清单8-10中的日志消息。如果读者连续访问以下许可证服务端点http://localhost:5555/api/licensing/v1/organizations/e254f8c-c442-4ebe-a82a-e2fc1d1ff78a/licenses/f3831f8c-c338-4ebe-a82a-e2fc1d1ff78a两次,那么应该在日志中看到以下两个输出语句:
    
licensingservice_1    | 2016-10-26 09:10:18.455 DEBUG 28 --- [nio-8080-exec-      1] c.t.l.c.OrganizationRestTemplateClient   : Unable to locate      organization from the redis cache: e254f8c-c442-4ebe-a82a-e2fc1d1ff78a. 
licensingservice_1    | 2016-10-26 09:10:31.602 DEBUG 28 --- [nio-8080-exec-      2] c.t.l.c.OrganizationRestTemplateClient   : I have successfully      retrieved an organization e254f8c-c442-4ebe-a82a-e2fc1d1ff78a from the      redis cache: com.thoughtmechanix.licenses.model.Organization@6d20d301
    
来自控制台的第一行显示,第一次调用尝试为组织访问许可证服务端点e254f8c-c442-4ebe-a82a-e2fc1d1ff78a。许可证服务首先检查了Redis缓存,但找不到要查找的组织记录。
然后代码调用组织服务来检索数据。从控制台显示出来的第二行表明,在第二次访问许可证服务端点时,组织记录已被缓存了。
注意本地运行的时候需要修改几个地方:
1、OrganizationRestTemplateClient.java的getOrganization方法:
2、licensingservice.yml文件中的redis.server: "redis"要改成:redis.server: "localhost"
 
 
3、整个项目要启动kafka、redis和zookeeper。
 
posted @ 2019-12-03 10:56  mongotea  阅读(502)  评论(0编辑  收藏  举报