欢迎光临
我们一直在努力

基于 Spring Cloud 的电商实时搜索与个性化推荐实战

目录

一、系统架构概览

1.1 微服务模块划分

1.2 数据流图

1.3 核心业务流程

二、技术选型与版本

三、项目搭建与关键代码

3.1 父工程依赖管理

3.2 商品服务 (product-service)

3.2.1 依赖

3.2.2 实体与 Repository

3.2.3 发送商品变更事件

3.3 搜索服务 (search-service)

3.3.1 依赖

3.3.2 Elasticsearch 配置

3.3.3 商品索引映射实体

3.3.4 监听 Kafka 消息,同步索引

3.3.5 搜索 API

3.4 用户行为服务 (user-action-service)

3.4.1 依赖

3.4.2 接收行为并发送到 Kafka

3.5 推荐服务 (recommend-service)

3.5.1 依赖

3.5.2 Redis 配置

3.5.3 消费行为消息,更新用户偏好到 Redis

3.5.4 推荐 API(基于用户偏好的商品召回)

3.6 API 网关 (gateway-service)

3.6.1 依赖

3.6.2 路由配置

3.7 服务注册中心 (eureka-server)

3.7.1 依赖

3.7.2 启动类与配置

四、配置与启动

4.1 基础设施准备

4.2 各服务配置文件

4.3 启动顺序

五、测试与验证

5.1 商品创建与索引同步

5.2 搜索测试

5.3 用户行为上报

5.4 推荐获取

六、扩展思考

七、总结


本文将带你搭建一个完整的电商微服务 DEMO,实现 实时商品搜索 和 基于用户行为的个性化推荐。核心技术栈:Spring Cloud (微服务治理) + Elasticsearch (搜索引擎) + Redis (实时缓存) + Kafka (消息队列)。

一、系统架构概览

1.1 微服务模块划分

服务名职责技术要点
Eureka Server 服务注册与发现 Spring Cloud Netflix Eureka
Spring Cloud Gateway API 网关,统一入口,路由转发 Spring Cloud Gateway
Product Service 商品核心服务(CRUD),商品变更时发送消息 MySQL, Spring Data JPA, Kafka
Search Service 监听商品变更消息,维护 ES 索引;提供商品搜索 API Elasticsearch, Kafka, RestHighLevelClient
User Action Service 接收用户前端行为(点击、购买等),发送到 Kafka Kafka, Spring Web
Recommend Service 消费用户行为消息,更新 Redis 中的用户偏好;提供个性化推荐 API Redis, Kafka, 简单推荐逻辑
Config Server 集中配置管理(可选) Spring Cloud Config

1.2 数据流图

用户前端
│
├─► 搜索请求 ── Gateway ──► Search Service ──► Elasticsearch
│
├─► 行为上报 ── Gateway ──► User Action Service ──► Kafka ──► Recommend Service ──► Redis
│
└─► 推荐请求 ── Gateway ──► Recommend Service ──► (Redis偏好 + ES查询)

1.3 核心业务流程

  • 商品数据同步:Product Service 对商品进行增删改后,发送 ProductChangeEvent 到 Kafka。Search Service 消费该事件,同步更新 Elasticsearch 中的商品索引。

  • 用户行为收集:用户在前端点击商品、购买等行为,通过 API 上报给 User Action Service,后者将行为消息发送到 Kafka(topic: user-actions)。

  • 用户偏好实时计算:Recommend Service 消费 user-actions,更新 Redis 中的用户实时偏好(例如最近浏览的商品分类、品牌等)。

  • 个性化推荐:当用户请求推荐时,Recommend Service 从 Redis 读取该用户的偏好(如分类 ID),然后去 Elasticsearch 查询同分类下的热门商品,返回给用户。

  • 实时搜索:用户输入关键词,Search Service 直接查询 Elasticsearch,返回分页商品列表。

  • 二、技术选型与版本

    • Spring Boot 3.1.5

    • Spring Cloud 2022.0.4

    • Elasticsearch 8.11.0 (使用官方 Java API Client)

    • Redis 7.x (Spring Data Redis)

    • Kafka 3.5.x (Spring Kafka / Spring Cloud Stream)

    • MySQL 8.0 (仅 Product Service 使用)

    • Maven 3.8+

    注:Spring Boot 3.x 已支持 Jakarta EE 9+,使用 jakarta.* 包。

    三、项目搭建与关键代码

    3.1 父工程依赖管理

    <!– pom.xml (parent) –>
    <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.1.5</version>
    <relativePath/>
    </parent>

    <properties>
    <spring-cloud.version>2022.0.4</spring-cloud.version>
    </properties>

    <dependencyManagement>
    <dependencies>
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-dependencies</artifactId>
    <version>${spring-cloud.version}</version>
    <type>pom</type>
    <scope>import</scope>
    </dependency>
    </dependencies>
    </dependencyManagement>

    3.2 商品服务 (product-service)

    3.2.1 依赖

    <dependencies>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <scope>runtime</scope>
    </dependency>
    <dependency>
    <groupId>org.springframework.kafka</groupId>
    <artifactId>spring-kafka</artifactId>
    </dependency>
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    </dependencies>

    3.2.2 实体与 Repository

    @Entity
    @Table(name = "product")
    public class Product {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String description;
    private BigDecimal price;
    private Long categoryId;
    private String brand;
    // getters/setters
    }

    public interface ProductRepository extends JpaRepository<Product, Long> {}

    3.2.3 发送商品变更事件

    @Service
    public class ProductService {
    @Autowired
    private ProductRepository productRepository;
    @Autowired
    private KafkaTemplate<String, Object> kafkaTemplate;

    public Product save(Product product) {
    Product saved = productRepository.save(product);
    // 发送事件到 Kafka
    ProductChangeEvent event = new ProductChangeEvent(saved.getId(), "CREATE");
    kafkaTemplate.send("product-change", event);
    return saved;
    }

    // 更新、删除同理
    }

    // 简单的 POJO 事件
    public record ProductChangeEvent(Long productId, String type) {}

    3.3 搜索服务 (search-service)

    3.3.1 依赖

    <dependencies>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
    <groupId>org.springframework.kafka</groupId>
    <artifactId>spring-kafka</artifactId>
    </dependency>
    <dependency>
    <groupId>co.elastic.clients</groupId>
    <artifactId>elasticsearch-java</artifactId>
    <version>8.11.0</version>
    </dependency>
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    </dependencies>

    3.3.2 Elasticsearch 配置

    @Configuration
    public class ElasticsearchConfig {
    @Bean
    public RestClient restClient() {
    return RestClient.builder(HttpHost.create("http://localhost:9200")).build();
    }

    @Bean
    public ElasticsearchTransport elasticsearchTransport(RestClient restClient) {
    return new RestClientTransport(restClient, new JacksonJsonpMapper());
    }

    @Bean
    public ElasticsearchClient elasticsearchClient(ElasticsearchTransport transport) {
    return new ElasticsearchClient(transport);
    }
    }

    3.3.3 商品索引映射实体

    public class EsProduct {
    private Long id;
    private String name;
    private String description;
    private BigDecimal price;
    private Long categoryId;
    private String brand;
    // getters/setters
    }

    3.3.4 监听 Kafka 消息,同步索引

    @Component
    public class ProductChangeConsumer {
    @Autowired
    private ElasticsearchClient esClient;

    @KafkaListener(topics = "product-change", groupId = "search-service")
    public void handleProductChange(ProductChangeEvent event) {
    if ("CREATE".equals(event.type()) || "UPDATE".equals(event.type())) {
    // 从数据库或直接接收完整商品信息(此处简化,假设消息包含商品完整信息)
    // 实际中可能需要调用 product-service API 获取最新数据
    EsProduct esProduct = fetchProductFromDb(event.productId());
    indexProduct(esProduct);
    } else if ("DELETE".equals(event.type())) {
    deleteProductIndex(event.productId());
    }
    }

    private void indexProduct(EsProduct product) {
    IndexRequest<EsProduct> request = IndexRequest.of(i -> i
    .index("products")
    .id(product.getId().toString())
    .document(product)
    );
    esClient.index(request);
    }

    // 其他方法…
    }

    3.3.5 搜索 API

    @RestController
    @RequestMapping("/search")
    public class SearchController {
    @Autowired
    private ElasticsearchClient esClient;

    @GetMapping("/products")
    public List<EsProduct> search(@RequestParam String keyword,
    @RequestParam(defaultValue = "0") int page,
    @RequestParam(defaultValue = "10") int size) throws IOException {
    SearchResponse<EsProduct> response = esClient.search(s -> s
    .index("products")
    .query(q -> q
    .bool(b -> b
    .should(should -> should.match(m -> m.field("name").query(keyword)))
    .should(should -> should.match(m -> m.field("description").query(keyword)))
    )
    )
    .from(page)
    .size(size),
    EsProduct.class
    );
    return response.hits().hits().stream().map(Hit::source).collect(Collectors.toList());
    }
    }

    3.4 用户行为服务 (user-action-service)

    3.4.1 依赖

    <dependencies>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
    <groupId>org.springframework.kafka</groupId>
    <artifactId>spring-kafka</artifactId>
    </dependency>
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    </dependencies>

    3.4.2 接收行为并发送到 Kafka

    @RestController
    @RequestMapping("/actions")
    public class UserActionController {
    @Autowired
    private KafkaTemplate<String, Object> kafkaTemplate;

    @PostMapping
    public void recordAction(@RequestBody UserAction action) {
    kafkaTemplate.send("user-actions", action);
    }
    }

    public record UserAction(Long userId, String actionType, Long productId, Long categoryId, Long timestamp) {}

    3.5 推荐服务 (recommend-service)

    3.5.1 依赖

    <dependencies>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
    <groupId>org.springframework.kafka</groupId>
    <artifactId>spring-kafka</artifactId>
    </dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <dependency>
    <groupId>co.elastic.clients</groupId>
    <artifactId>elasticsearch-java</artifactId>
    <version>8.11.0</version>
    </dependency>
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    </dependencies>

    3.5.2 Redis 配置

    @Configuration
    public class RedisConfig {
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
    RedisTemplate<String, Object> template = new RedisTemplate<>();
    template.setConnectionFactory(factory);
    template.setKeySerializer(new StringRedisSerializer());
    template.setValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class));
    return template;
    }
    }

    3.5.3 消费行为消息,更新用户偏好到 Redis

    @Component
    public class UserActionConsumer {
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @KafkaListener(topics = "user-actions", groupId = "recommend-service")
    public void consume(UserAction action) {
    String key = "user:pref:" + action.userId();
    // 简单偏好:最近浏览的分类ID(使用 Set 存储,并记录时间戳,此处简化)
    redisTemplate.opsForSet().add(key, action.categoryId());
    // 设置过期时间(例如7天)
    redisTemplate.expire(key, 7, TimeUnit.DAYS);
    // 也可以使用 ZSet 存储带时间的浏览历史,这里不再展开
    }
    }

    3.5.4 推荐 API(基于用户偏好的商品召回)

    @RestController
    @RequestMapping("/recommend")
    public class RecommendController {
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    @Autowired
    private ElasticsearchClient esClient;

    @GetMapping("/{userId}")
    public List<EsProduct> recommend(@PathVariable Long userId,
    @RequestParam(defaultValue = "10") int size) throws IOException {
    // 1. 从 Redis 获取用户最近浏览的分类ID(这里取一个,实际可多个)
    Set<Object> categoryIds = redisTemplate.opsForSet().members("user:pref:" + userId);
    if (categoryIds == null || categoryIds.isEmpty()) {
    return Collections.emptyList();
    }
    Long categoryId = (Long) categoryIds.iterator().next(); // 简化,取第一个

    // 2. 从 ES 查询该分类下的商品,按某种策略排序(如随机或热度)
    SearchResponse<EsProduct> response = esClient.search(s -> s
    .index("products")
    .query(q -> q.term(t -> t.field("categoryId").value(categoryId)))
    .size(size),
    EsProduct.class
    );
    return response.hits().hits().stream().map(Hit::source).collect(Collectors.toList());
    }
    }

    3.6 API 网关 (gateway-service)

    3.6.1 依赖

    <dependencies>
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    </dependencies>

    3.6.2 路由配置

    spring:
    cloud:
    gateway:
    routes:
    – id: product-service
    uri: lb://product-service
    predicates:
    – Path=/product/**
    – id: search-service
    uri: lb://search-service
    predicates:
    – Path=/search/**
    – id: user-action-service
    uri: lb://user-action-service
    predicates:
    – Path=/actions/**
    – id: recommend-service
    uri: lb://recommend-service
    predicates:
    – Path=/recommend/**

    3.7 服务注册中心 (eureka-server)

    3.7.1 依赖

    <dependencies>
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
    </dependency>
    </dependencies>

    3.7.2 启动类与配置

    @SpringBootApplication
    @EnableEurekaServer
    public class EurekaServerApplication {
    public static void main(String[] args) {
    SpringApplication.run(EurekaServerApplication.class, args);
    }
    }
    # application.yml
    server:
    port: 8761
    eureka:
    client:
    register-with-eureka: false
    fetch-registry: false

    四、配置与启动

    4.1 基础设施准备

    • 启动 MySQL,创建数据库 ecommerce,并执行建表语句。

    • 启动 Elasticsearch(8.x 需要配置安全,可暂时禁用)。

    • 启动 Redis。

    • 启动 Kafka(需要 Zookeeper)。

    4.2 各服务配置文件

    每个服务的 application.yml 需要包含:

    • 服务名

    • Eureka 客户端配置

    • Kafka 配置(bootstrap-servers)

    • 各自的数据源/ES/Redis 配置

    以 search-service 为例:

    spring:
    application:
    name: search-service
    kafka:
    bootstrap-servers: localhost:9092
    consumer:
    group-id: search-service
    key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
    value-deserializer: org.springframework.kafka.support.serializer.JsonDeserializer
    properties:
    spring.json.trusted.packages: '*'
    elasticsearch:
    uris: http://localhost:9200

    eureka:
    client:
    service-url:
    defaultZone: http://localhost:8761/eureka/

    4.3 启动顺序

  • Eureka Server

  • 其余所有微服务(product, search, user-action, recommend, gateway)

  • 五、测试与验证

    5.1 商品创建与索引同步

    curl http://localhost:9200/products/_search?pretty
    # 创建商品
    curl -X POST http://localhost:8080/product -H "Content-Type: application/json" -d '{
    "name": "华为Mate 60 Pro",
    "description": "鸿蒙系统,卫星通信",
    "price": 6999.00,
    "categoryId": 1,
    "brand": "华为"
    }'

    等待片刻,查看 Elasticsearch 索引:

    curl http://localhost:9200/products/_search?pretty

    5.2 搜索测试

    curl "http://localhost:8080/search/products?keyword=华为&page=0&size=5"

    应返回刚创建的商品。

    5.3 用户行为上报

    curl -X POST http://localhost:8080/actions -H "Content-Type: application/json" -d '{
    "userId": 1001,
    "actionType": "VIEW",
    "productId": 1,
    "categoryId": 1,
    "timestamp": 1700000000000
    }'

    5.4 推荐获取

    curl "http://localhost:8080/recommend/1001?size=5"

    应返回 categoryId=1 的商品。

    六、扩展思考

    • 推荐算法升级:可使用 Spark MLlib 或 TensorFlow 构建离线模型,通过 Kafka 推送实时特征。

    • 数据一致性:可使用 Kafka 事务和幂等生产者保证 Exactly-Once。

    • 性能优化:ES 查询可使用缓存,Redis 可存储更复杂的用户画像(如偏好向量)。

    • 监控:集成 Spring Boot Admin 或 Prometheus + Grafana。

    七、总结

    本文搭建了一个完整的电商微服务 DEMO,涵盖了 实时搜索 和 个性化推荐 的核心流程。通过 Spring Cloud 实现了服务治理,利用 Elasticsearch 提供高性能搜索,Redis 缓存用户实时偏好,Kafka 解耦异步数据流。该架构具有良好的扩展性,可作为真实电商系统的技术原型。

    赞(0)
    未经允许不得转载:171主机测评 » 基于 Spring Cloud 的电商实时搜索与个性化推荐实战
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址