Software Development

Achieving Strong Consistency in Distributed Spring Boot Applications with Redpanda

Redpanda is a modern, Kafka-compatible streaming platform designed for high performance and low latency. As a drop-in replacement for Apache Kafka, it offers several advantages:

  • Simplified architecture (single binary, no ZooKeeper dependency)
  • Improved performance (lower latency, higher throughput)
  • Full Kafka API compatibility (works with existing Kafka clients and tools)
  • Strong consistency guarantees out of the box

1. Strong Consistency Patterns with Spring Boot and Redpanda

1. Transactional Messaging Pattern

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class KafkaConfig {
 
    @Bean
    public ProducerFactory<String, String> producerFactory() {
        Map<String, Object> configProps = new HashMap<>();
        configProps.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "redpanda:9092");
        configProps.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
        configProps.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
        configProps.put(ProducerConfig.TRANSACTIONAL_ID_CONFIG, "tx-1");
        configProps.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, "true");
        return new DefaultKafkaProducerFactory<>(configProps);
    }
 
    @Bean
    public KafkaTransactionManager<String, String> kafkaTransactionManager(
            ProducerFactory<String, String> producerFactory) {
        return new KafkaTransactionManager<>(producerFactory);
    }
}
 
@Service
public class OrderService {
 
    @Autowired
    private KafkaTemplate<String, String> kafkaTemplate;
     
    @Autowired
    private OrderRepository orderRepository;
     
    @Transactional("kafkaTransactionManager")
    public void processOrder(Order order) {
        // Database operation
        orderRepository.save(order);
         
        // Kafka operation in the same transaction
        kafkaTemplate.send("orders", order.getId(), order.toJson());
    }
}

2. Outbox Pattern for Strong Consistency

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
@Configuration
@Entity
public class OutboxEvent {
    @Id
    @GeneratedValue
    private Long id;
     
    private String aggregateType;
    private String aggregateId;
    private String eventType;
    private String payload;
    private LocalDateTime timestamp;
    private boolean published;
}
 
@Service
public class OrderService {
 
    @Transactional
    public void createOrder(Order order) {
        // 1. Save order to database
        orderRepository.save(order);
         
        // 2. Save event to outbox table in the same transaction
        OutboxEvent event = new OutboxEvent();
        event.setAggregateType("Order");
        event.setAggregateId(order.getId());
        event.setEventType("OrderCreated");
        event.setPayload(order.toJson());
        event.setTimestamp(LocalDateTime.now());
        event.setPublished(false);
        outboxRepository.save(event);
    }
}
 
@Component
public class OutboxProcessor {
 
    @Scheduled(fixedRate = 1000)
    @Transactional
    public void processOutbox() {
        List<OutboxEvent> events = outboxRepository.findByPublishedFalse();
         
        events.forEach(event -> {
            kafkaTemplate.send("orders", event.getAggregateId(), event.getPayload());
            event.setPublished(true);
            outboxRepository.save(event);
        });
    }
}

2. Redpanda-Specific Optimizations

1. Leveraging Redpanda’s Linearizable Reads

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
@Configuration
public class ConsumerConfig {
 
    @Bean
    public ConsumerFactory<String, String> consumerFactory() {
        Map<String, Object> props = new HashMap<>();
        props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "redpanda:9092");
        props.put(ConsumerConfig.GROUP_ID_CONFIG, "order-group");
        props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
        props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
        props.put(ConsumerConfig.ISOLATION_LEVEL_CONFIG, "read_committed");
        // Redpanda specific optimization
        props.put("enable.linearizable.reads", "true");
        return new DefaultKafkaConsumerFactory<>(props);
    }
}

2. High-Performance Consumer Configuration

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
@KafkaListener(topics = "orders", groupId = "order-group")
public void listen(Order order) {
    // Process order with strong consistency guarantees
    orderProcessingService.process(order);
}
 
@Bean
public ConcurrentKafkaListenerContainerFactory<String, String> kafkaListenerContainerFactory() {
    ConcurrentKafkaListenerContainerFactory<String, String> factory =
        new ConcurrentKafkaListenerContainerFactory<>();
    factory.setConsumerFactory(consumerFactory());
    factory.setConcurrency(4); // Tune based on Redpanda partition count
    factory.getContainerProperties().setAckMode(ContainerProperties.AckMode.MANUAL_IMMEDIATE);
    return factory;
}

3. Monitoring and Observability

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
@Configuration
public class MetricsConfig {
 
    @Bean
    public MicrometerProducerListener<String, String> micrometerProducerListener(MeterRegistry registry) {
        return new MicrometerProducerListener<>(registry);
    }
 
    @Bean
    public MicrometerConsumerListener<String, String> micrometerConsumerListener(MeterRegistry registry) {
        return new MicrometerConsumerListener<>(registry);
    }
}
 
// In application.properties:
management.endpoints.web.exposure.include=health,info,metrics
management.metrics.export.prometheus.enabled=true

4. Deployment Considerations

  1. Redpanda Cluster Sizing: Start with 3 nodes for production deployments
  2. Resource Allocation: Redpanda benefits from more CPU than Kafka
  3. Storage: Use direct-attached storage for best performance
  4. Networking: Low-latency networking is crucial for strong consistency

5. Comparison with Traditional Kafka

FeatureRedpandaApache Kafka
ArchitectureSingle binary, no ZooKeeperRequires ZooKeeper
PerformanceLower latency, higher throughputGood performance with tuning
ConsistencyStrong by defaultConfigurable
Operational ComplexitySimplifiedMore complex
CompatibilityFull Kafka API compatibilityN/A

6. Conclusion

Redpanda provides an excellent alternative to Kafka for Spring Boot applications requiring strong consistency in distributed environments. Its simplified architecture and performance characteristics make it particularly suitable for high-throughput, low-latency systems while maintaining the familiar Kafka API that Spring developers are accustomed to.

Eleftheria Drosopoulou

Eleftheria is an Experienced Business Analyst with a robust background in the computer software industry. Proficient in Computer Software Training, Digital Marketing, HTML Scripting, and Microsoft Office, they bring a wealth of technical skills to the table. Additionally, she has a love for writing articles on various tech subjects, showcasing a talent for translating complex concepts into accessible content.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button