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;
import com.thoughtmechanix.licenses.model.Organization;
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 com.thoughtmechanix.licenses.model.Organization;
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方法:
将url:"http://zuulservice/api/organization/v1/organizations/{organizationId}"
修改为"http://192.168.237.132:5555/api/organizationservice/v1/organizations/{organizationId}"
2、licensingservice.yml文件中的redis.server: "redis"要改成:redis.server: "localhost"
3、整个项目要启动kafka、redis和zookeeper。