了解如何將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通常被認為是緩存存儲,但是我希望閱讀本文后你會對此有所改變。