SpringBoot与Cassandra整合,实现高写入吞吐量用户事件存储系统

随着我们的app使用人数快速发展,用户行为数据变得越来越重要,这些数据可以用来优化产品设计、提升用户体验、实施精准营销策略,并支持业务决策。然而,处理海量的用户行为数据需要高效的存储和分析能力,传统的数据库方案往往难以满足这些需求。

为什么选择使用Cassandra作为用户事件存储解决方案?

  • 高性能写入:Cassandra的设计使得它非常适合高写入场景。它的写操作是无锁的,并且所有节点都可以接受写请求,这大大提高了写入性能。

  • 一致性模型:Cassandra提供了多种一致性级别(如ONE, QUORUM, ALL等),可以根据具体需求调整,以平衡一致性和可用性。

  • 自动分片:Cassandra会自动将数据分布在集群中的多个节点上,确保负载均衡。

  • 动态添加节点:可以随时向集群中添加新的节点,以提高容量和性能,而无需停机维护。

  • 快速读取:Cassandra支持高效的点查询和范围查询,适合读取特定用户的所有事件。

  • 二级索引:虽然Cassandra不支持复杂的查询,但它提供了一定程度的二级索引功能,可以帮助进行更灵活的查询。

  • 多副本复制:Cassandra默认会在多个节点之间复制数据,确保数据的安全性和可靠性。

  • 强容错能力:即使某些节点宕机,其他节点仍然可以继续提供服务。

  • 定期快照和增量备份:支持定期快照和增量备份,方便数据恢复和灾难恢复。

  • 免费开源:作为Apache软件基金会的一个顶级项目,Cassandra是免费且开源的。

  • 硬件利用率高:Cassandra能够充分利用现有的硬件资源,减少额外的成本投入。

哪些公司选择Cassandra?

  • Capital One:用于存储金融交易数据和客户行为分析。

  • Spotify:用于存储音乐播放记录、用户偏好等数据。

  • Rovio (Angry Birds):用于存储游戏内玩家的行为数据和统计信息。

  • Uber:用于存储司机和乘客的位置数据、行程信息等。

  • Netflix:用于存储和处理大量的用户行为数据,如观看历史、搜索记录等。

  • Instagram:用于存储照片和视频的元数据,以及用户活动日志。

  • Apple:用于移动设备的推送通知系统和其他内部应用的数据存储。

  • Reddit:用于存储用户提交的内容、评论和其他社交数据。

  • Zynga:用于存储在线游戏中的用户活动和统计数据。

代码实操

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.0.5</version>
        <relativePath/><!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>cassandra-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>cassandra-demo</name>
    <description>Demo project for Spring Boot and Cassandra integration</description>
    <properties>
        <java.version>17</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-cassandra</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

application.properties

# 配置Cassandra连接信息
spring.data.cassandra.contact-points=localhost # Cassandra节点地址
spring.data.cassandra.port=9042 # Cassandra端口号
spring.data.cassandra.keyspace-name=user_events # keyspace名称
spring.data.cassandra.schema-action=create_if_not_exists # 如果keyspace不存在则创建

实体类

package com.example.cassandrareactivedemo.model;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.Id;
import org.springframework.data.cassandra.core.mapping.Table;

import java.util.Date;

/**
 * 用户事件实体类
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@Table("user_events")
publicclass UserEvent {
    @Id// 标记为主键
    private String eventId; // 事件ID
    private String userId; // 用户ID
    private String eventType; // 事件类型
    private Date eventTime; // 事件发生时间
}

Repository

package com.example.cassandrareactivedemo.repository;

import com.example.cassandrareactivedemo.model.UserEvent;
import org.springframework.data.cassandra.repository.ReactiveCassandraRepository;
import reactor.core.publisher.Flux;

public interface UserEventRepository extends ReactiveCassandraRepository<UserEvent, String> {
    /**
     * 根据用户ID查找所有相关的用户事件
     * @param userId 用户ID
     * @return 包含所有匹配用户的事件流
     */
    Flux<UserEvent> findByUserId(String userId);
}

Service

package com.example.cassandrareactivedemo.service;

import com.example.cassandrareactivedemo.model.UserEvent;
import com.example.cassandrareactivedemo.repository.UserEventRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

/**
 * 用户事件服务类,处理业务逻辑
 */
@Service
publicclass UserEventService {

    @Autowired
    private UserEventRepository userEventRepository;

    /**
     * 保存用户事件到Cassandra数据库
     * @param userEvent 用户事件对象
     * @return 包含已保存用户的Mono对象
     */
    public Mono<UserEvent> save(UserEvent userEvent) {
        return userEventRepository.save(userEvent);
    }

    /**
     * 根据用户ID查找所有相关的用户事件
     * @param userId 用户ID
     * @return 包含所有匹配用户的事件流
     */
    public Flux<UserEvent> findByUserId(String userId) {
        return userEventRepository.findByUserId(userId);
    }
}

Controller

package com.example.cassandrareactivedemo.controller;

import com.example.cassandrareactivedemo.model.UserEvent;
import com.example.cassandrareactivedemo.service.UserEventService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;


@RestController
@RequestMapping("/api/user-events")
publicclass UserEventController {

    @Autowired
    private UserEventService userEventService;

    /**
     * 创建新的用户事件
     * @param userEvent 用户事件对象
     * @return 包含新创建用户的Mono对象
     */
    @PostMapping("/")
    public Mono<UserEvent> createUserEvent(@RequestBody UserEvent userEvent) {
        return userEventService.save(userEvent);
    }

    /**
     * 根据用户ID获取所有相关用户事件
     * @param userId 用户ID
     * @return 包含所有匹配用户的事件流
     */
    @GetMapping("/{userId}")
    public Flux<UserEvent> getUserEventsByUserId(@PathVariable String userId) {
        return userEventService.findByUserId(userId);
    }
}

Application

package com.example.cassandrareactivedemo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class CassandraDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(CassandraDemoApplication.class, args);
    }
}

测试

创建新的用户事件

curl -X POST http://localhost:8080/api/user-events/ \
-H "Content-Type: application/json" \
-d '{
    "eventId": "event1",
    "userId": "user1",
    "eventType": "login",
    "eventTime": "2025-05-22T20:10:06Z"
}'

Respons

{
    "eventId": "event1",
    "userId": "user1",
    "eventType": "login",
    "eventTime": "2025-05-22T20:10:06Z"
}

获取特定用户的用户事件

curl -X GET http://localhost:8080/api/user-events/user1

Respons

[
    {
        "eventId": "event1",
        "userId": "user1",
        "eventType": "login",
        "eventTime": "2025-05-22T20:10:06Z"
    }
]

关注我,送Java福利

/**
 * 这段代码只有Java开发者才能看得懂!
 * 关注我微信公众号之后,
 * 发送:"效率手册",
 * 即可获得一本由清华大学为本校学生诚意出品
 * 《2025学年清华大学效率手册》Pdf电子书
 * 福利截止日期为2025年05月28日止
 * 手快有手慢没!!!
*/
System.out.println("请关注我的微信公众号:");
System.out.println("Java知识日历");
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值