微服务架构中的Redis
了解如何将redis与spring cloud和spring data一起使用以提供配置服务器,消息代理和数据库。
redis可以广泛用于微服务架构中。它可能是少数流行的软件解决方案之一,你的应用程序可以通过许多不同的方式来利用这些解决方案。根据要求,它可以充当主数据库,缓存或消息代理。虽然它也是键/值存储,但我们可以将其用作微服务体系结构中的配置服务器或发现服务器。尽管通常将其定义为内存中的数据结构,但我们也可以在持久模式下运行它。
通过这篇文章,我将结合我自己所掌握的和近期在优锐课学习到的知识,向你展示一些将redis与基于spring boot和spring cloud框架构建的微服务一起使用的示例。这些应用程序将使用redis发布/订阅,使用redis作为缓存或主数据库,最后使用redis作为配置服务器,彼此之间进行异步通信。这是说明所描述体系结构的图片。
redis作为配置服务器
如果你已经使用spring cloud构建了微服务,则可能对spring cloud config有所了解。它负责为微服务提供分布式配置模式。 不幸的是,spring cloud config不支持将redis作为属性源的后端存储库。这就是为什么我决定派生一个spring cloud config项目并实现此功能的原因。我希望我的实现将很快包含在spring cloud的官方发行版中,我们如何使用它?很简单的。让我们来看看。
spring boot的当前snapshot版本是2.2.0.build-snapshot
,与用于spring cloud config的版本相同。在构建spring cloud config server时,我们仅需要包括这两个依赖项,如下所示。
1 <parent> 2 <groupid>org.springframework.boot</groupid> 3 <artifactid>spring-boot-starter-parent</artifactid> 4 <version>2.2.0.build-snapshot</version> 5 </parent> 6 <artifactid>config-service</artifactid> 7 <groupid>pl.piomin.services</groupid> 8 <version>1.0-snapshot</version> 9 <dependencies> 10 <dependency> 11 <groupid>org.springframework.cloud</groupid> 12 <artifactid>spring-cloud-config-server</artifactid> 13 <version>2.2.0.build-snapshot</version> 14 </dependency> 15 </dependencies>
默认情况下,spring cloud config server使用一个git存储库后端。 我们需要激活一个redis
profile来强制它使用redis作为后端。 如果你的redis实例侦听的地址不是localhost:6379
,则需要使用spring.redis.*
属性覆盖自动配置的连接设置。 这是我们的bootstrap.yml
文件。
1 spring: 2 application: 3 name: config-service 4 profiles: 5 active: redis 6 redis: 7 host: 192.168.99.100
应用程序主类应使用@enableconfigserver
进行注释。
1 @springbootapplication 2 @enableconfigserver 3 public class configapplication { 4 public static void main(string[] args) { 5 new springapplicationbuilder(configapplication.class).run(args); 6 } 7 }
在运行应用程序之前,我们需要启动redis实例。这是将其作为docker容器运行并在端口6379上公开的命令。
1 $ docker run -d --name redis -p 6379:6379 redis
每个应用程序的配置都必须在键${spring.application.name}
或${spring.application.name}-${spring.profiles.active[n]}
可用。
我们必须使用与配置属性名称相对应的键来创建hash。我们的示例应用程序驱动程序管理使用三个配置属性:server.port
用于设置http侦听端口,spring.redis.host
用于更改用作消息代理和数据库的默认redis地址,以及sample.topic.name
用于设置名称。微服务之间用于异步通信的主题。这是我们为使用rdbtools可视化的驱动程序管理创建的redis hash的结构。
该可视化等效于运行redis cli命令hgetall
,该命令返回哈希中的所有字段和值。
1 >> hgetall driver-management 2 { 3 "server.port": "8100", 4 "sample.topic.name": "trips", 5 "spring.redis.host": "192.168.99.100" 6 }
在redis中设置键和值并使用有效的redis
profile运行spring cloud config server之后,我们需要在客户端启用分布式配置功能。为此,我们只需要将spring-cloud-starter-config
依赖项包含到每个微服务的thepom.xml
中即可。
1 <dependency> 2 <groupid>org.springframework.cloud</groupid> 3 <artifactid>spring-cloud-starter-config</artifactid> 4 </dependency>
我们使用spring cloud的最新稳定版本。
1 <dependencymanagement> 2 <dependencies> 3 <dependency> 4 <groupid>org.springframework.cloud</groupid> 5 <artifactid>spring-cloud-dependencies</artifactid> 6 <version>greenwich.sr1</version> 7 <type>pom</type> 8 <scope>import</scope> 9 </dependency> 10 </dependencies> 11 </dependencymanagement>
应用程序的名称是在启动时从属性spring.application.name
获取的,因此我们需要提供以下bootstrap.yml
文件。
1 spring: 2 application: 3 name: driver-management
redis作为消息代理
现在,我们可以继续研究基于微服务的体系结构中redis的第二个用例——消息代理。我们将实现一个典型的异步系统,如下图所示。在创建新行程并完成当前行程后,微服务行程管理会将通知发送到redis pub / sub。该通知由预订特定频道的驾驶员管理和乘客管理两者接收。
我们的申请非常简单。我们只需要添加以下依赖项即可提供rest api并与redis pub / sub集成。
1 <dependency> 2 <groupid>org.springframework.boot</groupid> 3 <artifactid>spring-boot-starter-web</artifactid> 4 </dependency> 5 <dependency> 6 <groupid>org.springframework.boot</groupid> 7 <artifactid>spring-boot-starter-data-redis</artifactid> 8 </dependency>
我们应该使用通道名称和发布者来注册bean。trippublisher
负责将消息发送到目标主题。
1 @configuration 2 public class tripconfiguration { 3 @autowired 4 redistemplate<?, ?> redistemplate; 5 @bean 6 trippublisher redispublisher() { 7 return new trippublisher(redistemplate, topic()); 8 } 9 @bean 10 channeltopic topic() { 11 return new channeltopic("trips"); 12 } 13 }
trippublisher
使用redistemplate
将消息发送到主题。 发送之前,它将使用jackson2jsonredisserializer
将对象中的所有消息转换为json字符串。
1 public class trippublisher { 2 private static final logger logger = loggerfactory.getlogger(trippublisher.class); 3 redistemplate<?, ?> redistemplate; 4 channeltopic topic; 5 public trippublisher(redistemplate<?, ?> redistemplate, channeltopic topic) { 6 this.redistemplate = redistemplate; 7 this.redistemplate.setvalueserializer(new jackson2jsonredisserializer(trip.class)); 8 this.topic = topic; 9 } 10 public void publish(trip trip) throws jsonprocessingexception { 11 logger.info("sending: {}", trip); 12 redistemplate.convertandsend(topic.gettopic(), trip); 13 } 14 }
我们已经在发布方实现了逻辑。现在,我们可以在订户端进行实施。我们有两个微服务驱动程序管理和乘客管理,它们侦听旅行管理微服务发送的通知。我们需要定义redismessagelistenercontainer
bean并设置消息侦听器实现类。
1 @configuration 2 public class driverconfiguration { 3 @autowired 4 redisconnectionfactory redisconnectionfactory; 5 @bean 6 redismessagelistenercontainer container() { 7 redismessagelistenercontainer container = new redismessagelistenercontainer(); 8 container.addmessagelistener(messagelistener(), topic()); 9 container.setconnectionfactory(redisconnectionfactory); 10 return container; 11 } 12 @bean 13 messagelisteneradapter messagelistener() { 14 return new messagelisteneradapter(new driversubscriber()); 15 } 16 @bean 17 channeltopic topic() { 18 return new channeltopic("trips"); 19 } 20 }
负责处理传入通知的类需要实现messagelistener
interface。 收到消息后,driversubscriber
会将其从json反序列化为对象,并更改驱动程序的状态。
1 @service 2 public class driversubscriber implements messagelistener { 3 private final logger logger = loggerfactory.getlogger(driversubscriber.class); 4 @autowired 5 driverrepository repository; 6 objectmapper mapper = new objectmapper(); 7 @override 8 public void onmessage(message message, byte[] bytes) { 9 try { 10 trip trip = mapper.readvalue(message.getbody(), trip.class); 11 logger.info("message received: {}", trip.tostring()); 12 optional<driver> optdriver = repository.findbyid(trip.getdriverid()); 13 if (optdriver.ispresent()) { 14 driver driver = optdriver.get(); 15 if (trip.getstatus() == tripstatus.done) 16 driver.setstatus(driverstatus.waiting); 17 else 18 driver.setstatus(driverstatus.busy); 19 repository.save(driver); 20 } 21 } catch (ioexception e) { 22 logger.error("error reading message", e); 23 } 24 } 25 }
redis作为主数据库
尽管使用redis的主要目的是内存中缓存或作为键/值存储,但它也可以充当应用程序的主数据库。在这种情况下,值得在持久模式下运行redis。
1 $ docker run -d --name redis -p 6379:6379 redis redis-server --appendonly yes
使用hash操作和mmap结构将实体存储在redis中。每个实体都需要具有hash键和id。
1 @redishash("driver") 2 public class driver { 3 @id 4 private long id; 5 private string name; 6 @geoindexed 7 private point location; 8 private driverstatus status; 9 // setters and getters ... 10 }
幸运的是,spring data redis为redis集成提供了众所周知的存储库模式。要启用它,我们应该使用@enableredisrepositories
注释配置或主类。当使用spring仓库模式时,我们不必自己构建对redis的任何查询。
1 @configuration 2 @enableredisrepositories 3 public class driverconfiguration { 4 // logic ... 5 }
使用spring data存储库,我们不必构建任何redis查询,只需遵循spring data约定的名称方法即可。有关更多详细信息,请参阅我以前的文章spring data redis简介。出于示例目的,我们可以使用spring data内部实现的默认方法。这是驱动程序管理中存储库接口的声明。
1 public interface driverrepository extends crudrepository<driver, long> {}
不要忘记通过使用@enableredisrepositories
注释主应用程序类或配置类来启用spring data存储库。
1 @configuration 2 @enableredisrepositories 3 public class driverconfiguration { 4 ... 5 }
结论
微服务架构中redis的使用案例多种多样。我刚刚介绍了如何轻松地将其与spring cloud和spring data一起使用以提供配置服务器,消息代理和数据库。redis通常被认为是缓存存储,但是我希望阅读本文后你会对此有所改变。