当前位置:首页 > 科技  > 软件

基于 DDD 的互联网“赞&踩”系统

来源: 责编: 时间:2023-10-30 09:07:06 228观看
导读该文是系统的用户手册,主要体现系统功能和所支持的高级特性,关于系统的核心设计正在整理中,请稍安勿躁1. 背景随着社交媒体的普及,用户生成的内容数量急剧增加。为了帮助用户更好地发现和分享内容,许多社交媒体平台都提供

该文是系统的用户手册,主要体现系统功能和所支持的高级特性,关于系统的核心设计正在整理中,请稍安勿躁M6K28资讯网——每日最新资讯28at.com

1. 背景

随着社交媒体的普及,用户生成的内容数量急剧增加。为了帮助用户更好地发现和分享内容,许多社交媒体平台都提供了赞/踩服务。M6K28资讯网——每日最新资讯28at.com

2. 目标

赞/踩服务是一种用户反馈机制。随着社交媒体的普及和发展,人们越来越喜欢在一种平台上分享自己的观点和生活,这时就需要一种形式化的反馈机制来快速评价这些信息的好坏。赞/踩服务的目标是为了提高用户互动性,增加内容的社会影响力,从而增加活跃用户数量。M6K28资讯网——每日最新资讯28at.com

3. 快速入门

系统所涉及的功能包括:M6K28资讯网——每日最新资讯28at.com

功能M6K28资讯网——每日最新资讯28at.com

描述M6K28资讯网——每日最新资讯28at.com

赞/踩M6K28资讯网——每日最新资讯28at.com

用户可以点击对应的赞或踩按钮,以表达自己的喜好或不喜好M6K28资讯网——每日最新资讯28at.com

取消赞/踩M6K28资讯网——每日最新资讯28at.com

用户可以取消之前的赞/踩,以更正自己的想法M6K28资讯网——每日最新资讯28at.com

计数器M6K28资讯网——每日最新资讯28at.com

赞和踩的数量都需要计数器,用于显示文章或评论的受欢迎程度和社交影响力等M6K28资讯网——每日最新资讯28at.com

赞/踩历史M6K28资讯网——每日最新资讯28at.com

用户可以查看自己赞/踩的历史记录,以查看自己对文章或评论的态度M6K28资讯网——每日最新资讯28at.com

3.1. 开发环境

基于 Spring Boot 框架进行开发,以 DDD 作为业务逻辑承载模型。M6K28资讯网——每日最新资讯28at.com

框架M6K28资讯网——每日最新资讯28at.com

版本M6K28资讯网——每日最新资讯28at.com

依赖说明M6K28资讯网——每日最新资讯28at.com

JDKM6K28资讯网——每日最新资讯28at.com

1.8+M6K28资讯网——每日最新资讯28at.com

运行环境M6K28资讯网——每日最新资讯28at.com

Spring BootM6K28资讯网——每日最新资讯28at.com

2.3.12.RELEASEM6K28资讯网——每日最新资讯28at.com


M6K28资讯网——每日最新资讯28at.com

Spring DataM6K28资讯网——每日最新资讯28at.com

2.3.9.RELEASEM6K28资讯网——每日最新资讯28at.com

基于 JPA 实现持久化;基于 Redis 完成缓存加速(可选)M6K28资讯网——每日最新资讯28at.com

LegoM6K28资讯网——每日最新资讯28at.com

0.1.22M6K28资讯网——每日最新资讯28at.com

DDD 模型落地M6K28资讯网——每日最新资讯28at.com

springfoxM6K28资讯网——每日最新资讯28at.com

3.0.0M6K28资讯网——每日最新资讯28at.com

文档管理M6K28资讯网——每日最新资讯28at.com

RocketMQM6K28资讯网——每日最新资讯28at.com

2.2.1M6K28资讯网——每日最新资讯28at.com

领域事件,异步处理(可选)M6K28资讯网——每日最新资讯28at.com

Sharding SphereM6K28资讯网——每日最新资讯28at.com

4.4.1M6K28资讯网——每日最新资讯28at.com

分库分表(可选)M6K28资讯网——每日最新资讯28at.com

3.2. 模块介绍

该项目使用标准的 “六边形架构”,对业务和技术进行分离,所以模块较多,但层次更为清晰。M6K28资讯网——每日最新资讯28at.com

模块M6K28资讯网——每日最新资讯28at.com

作用M6K28资讯网——每日最新资讯28at.com

domainM6K28资讯网——每日最新资讯28at.com

核心逻辑层,DDD 中核心组件,包括实体、值对象、聚合根、领域服务等M6K28资讯网——每日最新资讯28at.com

appM6K28资讯网——每日最新资讯28at.com

应用服务层,DDD 中的应用服务,主要负责流程编排M6K28资讯网——每日最新资讯28at.com

infrastructureM6K28资讯网——每日最新资讯28at.com

基础设施层,主要负责与 DB 或其他服务进行通讯M6K28资讯网——每日最新资讯28at.com

apiM6K28资讯网——每日最新资讯28at.com

RPC 服务中的接口定义,被 FeignClient 和 FeignService 依赖M6K28资讯网——每日最新资讯28at.com

FeignServiceM6K28资讯网——每日最新资讯28at.com

api 中接口的实际实现者,完成接口的适配M6K28资讯网——每日最新资讯28at.com

FeignClientM6K28资讯网——每日最新资讯28at.com

api 中Proxy实现者,方便使用方直接调用M6K28资讯网——每日最新资讯28at.com

bootstrapM6K28资讯网——每日最新资讯28at.com

应用启动入口,包括 Spring Boot 入口和所有配置M6K28资讯网——每日最新资讯28at.com

3.3. 启动项目

3.3.1. 建库建表

建表语句在infrastructure/src/main/resources/sql 目标下,包括单库和分库分表配置。单库建表语句如下:M6K28资讯网——每日最新资讯28at.com

create table dislike_action(    id          bigint auto_increment primary key,    create_time datetime    not null,    delete_time datetime    null,    update_time datetime    null,    vsn         int         not null,    status      char(16)    not null,    target_id   bigint      not null,    target_type varchar(16) not null,    user_id     bigint      not null,    constraint unq_user_target        unique (user_id, target_type, target_id));create table dislike_target_count(    id          bigint auto_increment primary key,    create_time datetime    not null,    delete_time datetime    null,    update_time datetime    null,    vsn         int         not null,    count       bigint      not null,    target_id   bigint      not null,    target_type varchar(16) not null,    constraint unq_target        unique (target_id, target_type));create table like_action(    id          bigint auto_increment primary key,    create_time datetime    not null,    delete_time datetime    null,    update_time datetime    null,    vsn         int         not null,    status      char(16)    not null,    target_id   bigint      not null,    target_type varchar(16) not null,    user_id     bigint      not null,    constraint unq_user_target        unique (user_id, target_type, target_id));create table like_target_count(    id          bigint auto_increment primary key,    create_time datetime    not null,    delete_time datetime    null,    update_time datetime    null,    vsn         int         not null,    count       bigint      not null,    target_id   bigint      not null,    target_type varchar(16) not null,    constraint unq_target        unique (target_id, target_type));

3.3.2. 修改数据库配置

修改bootstrap/src/main/resource/application.yml 增加数据配置,具体如下:M6K28资讯网——每日最新资讯28at.com

spring:  datasource:    driver-class-name: com.mysql.cj.jdbc.Driver    url: jdbc:mysql://127.0.0.1:3306/like    username: root    password: root

3.3.3. 启动应用程序

直接运行 bootstrap 模块下的 LikeApplication 类,输入地址:http://127.0.0.1:8080/swagger-ui/M6K28资讯网——每日最新资讯28at.com

当看到如下界面证明程序启动成功:M6K28资讯网——每日最新资讯28at.com

M6K28资讯网——每日最新资讯28at.com

3.3. 核心 API

核心接口如下:M6K28资讯网——每日最新资讯28at.com

功能M6K28资讯网——每日最新资讯28at.com

请求地址M6K28资讯网——每日最新资讯28at.com

参数类型M6K28资讯网——每日最新资讯28at.com

参数说明M6K28资讯网——每日最新资讯28at.com

返回结果M6K28资讯网——每日最新资讯28at.com

cur DemoM6K28资讯网——每日最新资讯28at.com

点赞M6K28资讯网——每日最新资讯28at.com

POST /feignService/action/command/likeM6K28资讯网——每日最新资讯28at.com

RequestBodyM6K28资讯网——每日最新资讯28at.com

{"userId": 用户id, "targetType": 目标对象类型,"targetId": 目标对象 id}M6K28资讯网——每日最新资讯28at.com

M6K28资讯网——每日最新资讯28at.com

curl -X POST "http://127.0.0.1:8080/feignService/action/command/like" -H "accept: /" -H "Content-Type: application/json" -d "{"targetId":1,"targetType":"TEST","userId":2}"M6K28资讯网——每日最新资讯28at.com

取消点赞M6K28资讯网——每日最新资讯28at.com

POST /feignService/action/command/unlikeM6K28资讯网——每日最新资讯28at.com

RequestBodyM6K28资讯网——每日最新资讯28at.com

{"userId": 用户id, "targetType": 目标对象类型,"targetId": 目标对象 id}M6K28资讯网——每日最新资讯28at.com

M6K28资讯网——每日最新资讯28at.com

curl -X POST "http://127.0.0.1:8080/feignService/action/command/unlike" -H "accept: /" -H "Content-Type: application/json" -d "{"targetId":1,"targetType":"test","userId":2}"M6K28资讯网——每日最新资讯28at.com

获取点赞数量M6K28资讯网——每日最新资讯28at.com

GET /feignService/targetCount/query/getLikeCountByTargetM6K28资讯网——每日最新资讯28at.com

RequestParamM6K28资讯网——每日最新资讯28at.com

type:目标类型;ids:目标id集合M6K28资讯网——每日最新资讯28at.com

[{"targetType":目标对象类型,“targetId":目标对象id,"count":点赞数量}]M6K28资讯网——每日最新资讯28at.com

curl -X GET "http://127.0.0.1:8080/feignService/targetCount/query/getLikeCountByTarget?type=test&ids=1" -H "accept: /"M6K28资讯网——每日最新资讯28at.com

获取点赞记录M6K28资讯网——每日最新资讯28at.com

GET /feignService/action/query/getLikeByUserAndTypeM6K28资讯网——每日最新资讯28at.com

RequestParamM6K28资讯网——每日最新资讯28at.com

type:目标类型;userId:userIdM6K28资讯网——每日最新资讯28at.com

[{"targetType":目标对象类型,“targetId":目标对象id,"userId":用户id,"valid":是否有效}]M6K28资讯网——每日最新资讯28at.com

curl -X GET "http://127.0.0.1:8080/feignService/action/query/getLikeByUserAndType?userId=2&type=test" -H "accept: /"M6K28资讯网——每日最新资讯28at.com

M6K28资讯网——每日最新资讯28at.com

POST /feignService/action/command/dislikeM6K28资讯网——每日最新资讯28at.com

RequestBodyM6K28资讯网——每日最新资讯28at.com

{"userId": 用户id, "targetType": 目标对象类型,"targetId": 目标对象 id}M6K28资讯网——每日最新资讯28at.com

M6K28资讯网——每日最新资讯28at.com

curl -X POST "http://127.0.0.1:8080/feignService/action/command/dislike" -H "accept: /" -H "Content-Type: application/json" -d "{"targetId":1,"targetType":"test","userId":2}"M6K28资讯网——每日最新资讯28at.com

取消踩M6K28资讯网——每日最新资讯28at.com

POST /feignService/action/command/unDislikeM6K28资讯网——每日最新资讯28at.com

RequestBodyM6K28资讯网——每日最新资讯28at.com

{"userId": 用户id, "targetType": 目标对象类型,"targetId": 目标对象 id}M6K28资讯网——每日最新资讯28at.com

M6K28资讯网——每日最新资讯28at.com

curl -X POST "http://127.0.0.1:8080/feignService/action/command/unDislike" -H "accept: /" -H "Content-Type: application/json" -d "{"targetId":1,"targetType":"test","userId":2}"M6K28资讯网——每日最新资讯28at.com

获取踩数量M6K28资讯网——每日最新资讯28at.com

GET /feignService/targetCount/query/getDislikeCountByTypeM6K28资讯网——每日最新资讯28at.com

RequestParamM6K28资讯网——每日最新资讯28at.com

type:目标类型;ids:目标id集合M6K28资讯网——每日最新资讯28at.com

[{"targetType":目标对象类型,“targetId":目标对象id,"count":点赞数量}]M6K28资讯网——每日最新资讯28at.com

curl -X GET "http://127.0.0.1:8080/feignService/targetCount/query/getDislikeCountByType?type=test&ids=1" -H "accept: /"M6K28资讯网——每日最新资讯28at.com

获取点赞记录M6K28资讯网——每日最新资讯28at.com

GET /feignService/action/query/getDislikeByUserAndTypeM6K28资讯网——每日最新资讯28at.com

RequestParamM6K28资讯网——每日最新资讯28at.com

type:目标类型;userId:userIdM6K28资讯网——每日最新资讯28at.com

[{"targetType":目标对象类型,“targetId":目标对象id,"userId":用户id,"valid":是否有效}]M6K28资讯网——每日最新资讯28at.com

curl -X GET "http://127.0.0.1:8080/feignService/action/query/getDislikeByUserAndType?userId=2&type=test" -H "accept: /"M6K28资讯网——每日最新资讯28at.com

核心API直接在 Swagger UI 上进行测试即可!!!M6K28资讯网——每日最新资讯28at.com

4. 高级特性

4.1. 自定义业务验证

流程中涉及两个重要的概念:M6K28资讯网——每日最新资讯28at.com

  • ActionUser: 操作 赞或踩 的用户;
  • ActionTarget: 赞或踩 的目的对象;

在实际业务场景,需要对这两个对象的有效性进行验证,比如:M6K28资讯网——每日最新资讯28at.com

  • ActionUser

用户是否存在?M6K28资讯网——每日最新资讯28at.com

用户状态是否有效?M6K28资讯网——每日最新资讯28at.com

是否是黑名单用户?M6K28资讯网——每日最新资讯28at.com

  • ActionTarget
  • 目标对象是否存在?
  • 目标对象是否已经下线/禁用?

这些功能扩展直接实现对应的 Loader 即可。M6K28资讯网——每日最新资讯28at.com

4.1.1. ActionUser 扩展

ActionUser 定义如下:M6K28资讯网——每日最新资讯28at.com

public class ActionUser {    @Column(name = "user_id", updatable = false)    private Long userId;    @Transient    private boolean valid;}

如果用户状态存在问题,直接将 valid 置为 false 即可。M6K28资讯网——每日最新资讯28at.com

ActionUserLoader 定义如下:M6K28资讯网——每日最新资讯28at.com

public interface ActionUserLoader {    ActionUser loadByUserId(Long userId);}

只需实现 ActionUserLoader 并注册为 Spring 托管 Bean 即可,具体如下:M6K28资讯网——每日最新资讯28at.com

@Component(value = LoadActionUserByUserId.BEAN_NAME)public class TestActionUserLoader implements ActionUserLoader {    @Override    public ActionUser loadByUserId(Long userId) {        if (userId == null || userId.longValue() < 0){            return ActionUser.apply(userId, false);        }else {            return ActionUser.apply(userId);        }    }}

当 userId 为 null 或者 小于 0 时,表明为无效用户,将 valid 设置为 false。M6K28资讯网——每日最新资讯28at.com

【备注】Bean 必须注册为LoadActionUserByUserId.BEAN_NAME(actionUserLoader),否则框架将无法识别。M6K28资讯网——每日最新资讯28at.com

4.1.2. ActionTarget 扩展

ActionTarget 定义如下:M6K28资讯网——每日最新资讯28at.com

public class ActionTarget {    @Column(name = "target_type", updatable = false)    private String type;    @Column(name = "target_id", updatable = false)    private Long id;    @Transient    private boolean valid;}

如果目标对象状态存在问题,直接将 valid 置为 false 即可。M6K28资讯网——每日最新资讯28at.com

由于系统中可以存在多种目标对象,为每个类型提供单独的 Loader,接口如下:M6K28资讯网——每日最新资讯28at.com

public interface SingleActionTargetLoader {    /**     * 是否支持 type 类型的 Target     * @param type     * @return     */    boolean support(String type);    /**     * 加载 Target 对象     * @param type     * @param id     * @return     */    ActionTarget load(String type, Long id);}

按需要实现接口,样例如下:M6K28资讯网——每日最新资讯28at.com

@Component@Order(0)public class TestActionTargetLoader        extends AbstractSingleActionTargetLoader        implements SingleActionTargetLoader {    public TestActionTargetLoader() {        super("Test");    }    @Override    protected ActionTarget doLoadById(String type, Long id) {        if (id == null || id.longValue() < 0){            return ActionTarget.apply(type, id, false);        }else {            return ActionTarget.apply(type, id);        }    }}

该实现对 type 为 Test 的 Target 进行加载。M6K28资讯网——每日最新资讯28at.com

4.2. 发布领域事件

领域事件是 DDD 中的重要概念,当系统发生状态变化后,将变化结果对外进行广播,从而实现系统间的集成。M6K28资讯网——每日最新资讯28at.com

4.2.1. 添加 RocketMQ

外部领域事件通过 RocketMQ 向外广播,需要搭建 RocketMQ 集群并在项目中增加 RocketMQ 的支持。M6K28资讯网——每日最新资讯28at.com

在 bootstrap 模块的 pom 中增加 rocketmq starter,具体如下:M6K28资讯网——每日最新资讯28at.com

<dependency>    <groupId>org.apache.rocketmq</groupId>    <artifactId>rocketmq-spring-boot-starter</artifactId></dependency>

在 application.yml 增加 rocketmq 的配置,具体如下:M6K28资讯网——每日最新资讯28at.com

rocketmq:  name-server: http://127.0.0.1:9876  producer:    group: like-service

至此,便完成了与 rocketmq 的集成。M6K28资讯网——每日最新资讯28at.com

4.2.2. 打开领域事件开关

在 application.yml 添加如下配置:M6K28资讯网——每日最新资讯28at.com

like:  event:    #开启领域事件    enable: true    #指定领域事件发送的 topic    topic: like-event-topic

开启领域事件,并指定事件发送的 topicM6K28资讯网——每日最新资讯28at.com

4.2.3. 测试领域事件

重新启动项目,当控制台输出以下表明配置成功:M6K28资讯网——每日最新资讯28at.com

Use RocketMQ to Publish Like EventM6K28资讯网——每日最新资讯28at.com

使用 swagger 运行 dislike 操作,从日志中可知消息发送成功:M6K28资讯网——每日最新资讯28at.com

M6K28资讯网——每日最新资讯28at.com

4.2.4. 支持领域事件

系统支持的领域事件包括:M6K28资讯网——每日最新资讯28at.com

领域事件类型M6K28资讯网——每日最新资讯28at.com

触发机制M6K28资讯网——每日最新资讯28at.com

tagM6K28资讯网——每日最新资讯28at.com

消息体M6K28资讯网——每日最新资讯28at.com

LikeMarkedEventM6K28资讯网——每日最新资讯28at.com

点赞成功M6K28资讯网——每日最新资讯28at.com

LikeMarkedEventM6K28资讯网——每日最新资讯28at.com

见 LikeMarkedEvent 类M6K28资讯网——每日最新资讯28at.com

LikeCancelledEventM6K28资讯网——每日最新资讯28at.com

取消点赞成功M6K28资讯网——每日最新资讯28at.com

LikeCancelledEventM6K28资讯网——每日最新资讯28at.com

见 LikeCancelledEvent 类M6K28资讯网——每日最新资讯28at.com

DislikeMarkedEventM6K28资讯网——每日最新资讯28at.com

踩成功M6K28资讯网——每日最新资讯28at.com

DislikeMarkedEventM6K28资讯网——每日最新资讯28at.com

见 DislikeMarkedEvent 类M6K28资讯网——每日最新资讯28at.com

DislikeCancelledEventM6K28资讯网——每日最新资讯28at.com

取消踩成功M6K28资讯网——每日最新资讯28at.com

DislikeCancelledEventM6K28资讯网——每日最新资讯28at.com

见 DislikeCancelledEvent 类M6K28资讯网——每日最新资讯28at.com

4.3. 缓存加速

在系统中,获取目标对象的 赞/踩 数量接口调用量最大,会成为系统的第一个性能卡点,针对这个问题,可以通过引入 redis 缓存进行性能加速。M6K28资讯网——每日最新资讯28at.com

4.3.1. 添加 redis 依赖

首先需要引入 redis 相关依赖,在 bootstrap 的 pom 中增加如下配置:M6K28资讯网——每日最新资讯28at.com

<dependency>    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-starter-data-redis</artifactId></dependency>

然后在 application.yml 中增加 redis 相关配置:M6K28资讯网——每日最新资讯28at.com

spring:  redis:    host: 127.0.0.1    port: 6379

4.3.2. 开启缓存

完成redis配置后,需要在 application.yml 开启对应的缓存,具体如下:M6K28资讯网——每日最新资讯28at.com

target:  count:    dislike:      cache:        # 是否开启缓存        enable: true    like:      cache:        # 是否开启缓存        enable: true

4.3.3. 缓存效果

未开启缓存前,每次查询数量都会执行一条 sql,具体如下:curl 命令如下:M6K28资讯网——每日最新资讯28at.com

curl -X GET "http://127.0.0.1:8080/feignService/targetCount/query/getDislikeCountByType?type=test&ids=1" -H "accept: */*"

输出 sql 如下:M6K28资讯网——每日最新资讯28at.com

Hibernate: select disliketar0_.id as id1_1_, disliketar0_.create_time as create_t2_1_, disliketar0_.delete_time as delete_t3_1_, disliketar0_.update_time as update_t4_1_, disliketar0_.vsn as vsn5_1_, disliketar0_.count as count6_1_, disliketar0_.target_id as target_i7_1_, disliketar0_.target_type as target_t8_1_ from dislike_target_count disliketar0_ where disliketar0_.target_type=? and (disliketar0_.target_id in (?))

开启缓存后,再次执行以上 curl,控制台不会输出sql,而是会输出一行日志:M6K28资讯网——每日最新资讯28at.com

c.g.l.i.s.RedisBasedTargetCountCache     : load All Data From Cache for test and [1]

说明缓存已经生效。M6K28资讯网——每日最新资讯28at.com

所有的 action 操作都会与同步的对缓存进行更新。M6K28资讯网——每日最新资讯28at.com

4.4. 异步存储

当目标对象出现热点时,会产生高并发请求,对于 action 来说,主要以数据插入和数据的分散更新为主。但对于 count,就会产生热点更新,从而成为系统的瓶颈。在这个场景,最适合的解决方案便是引入 MQ 对流量进行削峰填谷。M6K28资讯网——每日最新资讯28at.com

4.4.1. 增加 rocketmq

与 4.2.1. 添加 RocketMQ 内容一致,在此不再重复。M6K28资讯网——每日最新资讯28at.com

4.4.2. 开启异步化

在 application.yml 中增加如下配置:M6K28资讯网——每日最新资讯28at.com

target:  count:    dislike:      async:        # 是否开启异步更新        enable: true        # 异步更新所使用的 topic        topic: dislike-target-count-async-topic        # 异步更新使用的消费者组        consumerGroup: dislike-target-count-async-group    like:      async:        # 是否开启异步更新        enable: true        # 异步更新所使用的 topic        topic: like-target-count-async-topic        # 异步更新使用的消费者组        consumerGroup: like-target-count-async-group

4.4.3. 异步效果

重启应用程序,在 swagger 中执行 点赞 操作,从控制台可以看到如下日志:M6K28资讯网——每日最新资讯28at.com

[nio-8080-exec-7] c.g.l.c.a.order.OrderedAsyncInterceptor  : success to send orderly async Task to RocketMQ, args is [ActionTarget(type=test, id=18, valid=true), 1], shardingKey is 18, msg is GenericMessage [payload={"0":"{/"type/":/"test/",/"id/":18,/"valid/":true}","1":"1"}, headers={id=c84e6be5-acec-27c2-3f44-6250003a56c7, timestamp=1685275901638}], result is SendResult [sendStatus=SEND_OK, msgId=7F0000014F505C8DA9628F610AC60007, offsetMsgId=C0A8032300002A9F00000000001A0AFD, messageQueue=MessageQueue [topic=dislike-target-count-async-topic, brokerName=MacdeMacBook-Pro-171.local, queueId=3], queueOffset=8][MessageThread_4] g.l.i.d.DislikeTargetCountRepositoryImpl : begin to incr for db target ActionTarget(type=test, id=18, valid=true), count 1[nio-8080-exec-7] com.geekhalo.like.app.RocketMQPublisher  : success to send msg GenericMessage [payload={"targetId":18,"targetType":"test","userId":1}, headers={id=4e8e13f9-b3cd-7b90-059f-f506f09d9948, timestamp=1685275901640}] to like-event-topic:DislikeMarkedEvent, msgId is 7F0000014F505C8DA9628F610AC80008[nio-8080-exec-7] c.g.l.c.c.s.AbstractCommandService       : success to sync AbstractCommandService.Syncer.Data(id=106, action=UPDATE, a=DislikeAction(super=AbstractAction(super=AbstractAggRoot(super=AbstractEntity(vsn=0, createAt=Sun May 28 20:11:41 CST 2023, updateAt=Sun May 28 20:11:41 CST 2023, deleteAt=null), events=[]), id=106, user=ActionUser(userId=1, valid=true), target=ActionTarget(type=test, id=18, valid=true), status=VALID)))[MessageThread_4] g.l.i.d.DislikeTargetCountRepositoryImpl : success to incr for db target ActionTarget(type=test, id=18, valid=true), count 1[MessageThread_4] .s.AbstractSingleMethodConsumerContainer : consume message 7F0000014F505C8DA9628F610AC60007, cost: 27 ms

从日志上看,可以得出:M6K28资讯网——每日最新资讯28at.com

  • nio 线程向 MQ 发送消息
  • MessageThread 线程从 MQ 中获取数据并执行 incr 操作

4.5. 分库分表

随着系统的运行,数据量会逐渐增大,最终超出单个 DB 的容量上限。这种情况下,最佳实践便是对数据库进行分库分表。M6K28资讯网——每日最新资讯28at.com

4.5.1. 构建数据库和表

在 infrastructure 模块的 sql 目录下存在两个 sql 文件:M6K28资讯网——每日最新资讯28at.com

  • create_table_sharding_action.sql : 赞/踩 操作分库分表
  • create_table_sharding_count.sql : 赞/踩 计数分库分表

示例中总共分16张表,存放在两个数据库中:M6K28资讯网——每日最新资讯28at.com

  • db1 存放 0-7 表
  • db2 存放 8-15 表

如图所示:M6K28资讯网——每日最新资讯28at.com

4.5.2. 添加 ShardingSphere 支持

在 bootstrap 的pom 文件增加 ShardingSphere 的依赖,具体如下:M6K28资讯网——每日最新资讯28at.com

<dependency>    <groupId>org.apache.shardingsphere</groupId>    <artifactId>sharding-jdbc-spring-boot-starter</artifactId></dependency>

其次,增加分库分表配置文件,为了方便新建 application.properties 存放分库分表配置:M6K28资讯网——每日最新资讯28at.com

# 数据源配置# 总共4个数据源spring.shardingsphere.datasource.names=action-ds0, action-ds1, count-ds0, count-ds1 # action-ds0 数据源配置spring.shardingsphere.datasource.action-ds0.type=com.zaxxer.hikari.HikariDataSourcespring.shardingsphere.datasource.action-ds0.driver-class-name=com.mysql.cj.jdbc.Driverspring.shardingsphere.datasource.action-ds0.jdbc-url=jdbc:mysql://127.0.0.1:3306/like_action_0?allowPublicKeyRetrieval=true&useSSL=false&serverTimezone=UTCspring.shardingsphere.datasource.action-ds0.username=rootspring.shardingsphere.datasource.action-ds0.password=root# action-ds1 数据源配置spring.shardingsphere.datasource.action-ds1.type=com.zaxxer.hikari.HikariDataSourcespring.shardingsphere.datasource.action-ds1.driver-class-name=com.mysql.cj.jdbc.Driverspring.shardingsphere.datasource.action-ds1.jdbc-url=jdbc:mysql://127.0.0.1:3306/like_action_1?allowPublicKeyRetrieval=true&useSSL=false&serverTimezone=UTCspring.shardingsphere.datasource.action-ds1.username=rootspring.shardingsphere.datasource.action-ds1.password=root# count-ds0 数据源配置spring.shardingsphere.datasource.count-ds0.type=com.zaxxer.hikari.HikariDataSourcespring.shardingsphere.datasource.count-ds0.driver-class-name=com.mysql.cj.jdbc.Driverspring.shardingsphere.datasource.count-ds0.jdbc-url=jdbc:mysql://127.0.0.1:3306/like_count_0?allowPublicKeyRetrieval=true&useSSL=false&serverTimezone=UTCspring.shardingsphere.datasource.count-ds0.username=rootspring.shardingsphere.datasource.count-ds0.password=root# count-ds1 数据源配置spring.shardingsphere.datasource.count-ds1.type=com.zaxxer.hikari.HikariDataSourcespring.shardingsphere.datasource.count-ds1.driver-class-name=com.mysql.cj.jdbc.Driverspring.shardingsphere.datasource.count-ds1.jdbc-url=jdbc:mysql://127.0.0.1:3306/like_count_1?allowPublicKeyRetrieval=true&useSSL=false&serverTimezone=UTCspring.shardingsphere.datasource.count-ds1.username=rootspring.shardingsphere.datasource.count-ds1.password=root# 分库分表规则配置# 使用雪花算法生成分布式主键id的值spring.shardingsphere.sharding.default-key-generator.column=idspring.shardingsphere.sharding.default-key-generator.column-type=BIGINTspring.shardingsphere.sharding.default-key-generator.type=SNOWFLAKEspring.shardingsphere.sharding.default-key-generator.algorithm-expression=SNOWFLAKE_HASH(id, 12)spring.shardingsphere.sharding.default-key-generator.matrix-handling-type=SHARDING_DEFAULT# 踩行为表配置spring.shardingsphere.sharding.tables.dislike_action.actual-data-nodes=action-ds0.dislike_action_$->{0..7},action-ds1.dislike_action_$->{8..15}# user_id 为分表分片键spring.shardingsphere.sharding.tables.dislike_action.table-strategy.inline.sharding-column=user_id# 根据 user_id 以 16 取模,进行分表spring.shardingsphere.sharding.tables.dislike_action.table-strategy.inline.algorithm-expression=dislike_action_$->{Math.abs(user_id.hashCode())  % 16}# user_id 为分库分片键spring.shardingsphere.sharding.tables.dislike_action.database-strategy.inline.sharding-column=user_id# 根据 user_id 以 16 取模后除8 ,进行分库spring.shardingsphere.sharding.tables.dislike_action.database-strategy.inline.algorithm-expression=action-ds$->{Math.floorDiv((Math.abs(user_id.hashCode())  % 16) , 8)}spring.shardingsphere.sharding.tables.like_action.actual-data-nodes=action-ds0.like_action_$->{0..7},action-ds1.like_action_$->{8..15}spring.shardingsphere.sharding.tables.like_action.table-strategy.inline.sharding-column=user_idspring.shardingsphere.sharding.tables.like_action.table-strategy.inline.algorithm-expression=like_action_$->{Math.abs(user_id.hashCode())  % 16}spring.shardingsphere.sharding.tables.like_action.database-strategy.inline.sharding-column=user_idspring.shardingsphere.sharding.tables.like_action.database-strategy.inline.algorithm-expression=action-ds$->{Math.floorDiv((Math.abs(user_id.hashCode())  % 16) , 8)}# 计数表配置spring.shardingsphere.sharding.tables.dislike_target_count.actual-data-nodes=count-ds0.dislike_target_count_$->{0..7},count-ds1.dislike_target_count_$->{8..15}# target_id 为分表分片键spring.shardingsphere.sharding.tables.dislike_target_count.table-strategy.inline.sharding-column=target_id# 根据 target_id 以 16 取模,进行分表spring.shardingsphere.sharding.tables.dislike_target_count.table-strategy.inline.algorithm-expression=dislike_target_count_$->{Math.abs(target_id.hashCode())  % 16}# target_id 为分库分片键spring.shardingsphere.sharding.tables.dislike_target_count.database-strategy.inline.sharding-column=target_id# 根据 target_id 以 16 取模后除8 ,进行分库spring.shardingsphere.sharding.tables.dislike_target_count.database-strategy.inline.algorithm-expression=count-ds$->{Math.floorDiv((Math.abs(target_id.hashCode()) % 16), 8)}spring.shardingsphere.sharding.tables.like_target_count.actual-data-nodes=count-ds0.like_target_count_$->{0..7},count-ds1.like_target_count_$->{8..15}spring.shardingsphere.sharding.tables.like_target_count.table-strategy.inline.sharding-column=target_idspring.shardingsphere.sharding.tables.like_target_count.table-strategy.inline.algorithm-expression=like_target_count_$->{Math.abs(target_id.hashCode()) % 16}spring.shardingsphere.sharding.tables.like_target_count.database-strategy.inline.sharding-column=target_idspring.shardingsphere.sharding.tables.like_target_count.database-strategy.inline.algorithm-expression=count-ds$->{Math.floorDiv((Math.abs(target_id.hashCode()) % 16), 8)}# 打印 SQL 配置(可选)spring.shardingsphere.props.sql.show=true

在雪花算法情况下,尾数会变的极度不均匀,所以在进行计算之前,通常先执行 hashCode 在进行取模操作。M6K28资讯网——每日最新资讯28at.com

4.5.3. 分库分表效果

启动应用程序,控制台输出 sharding 相关配置,具体如下:M6K28资讯网——每日最新资讯28at.com

defaultKeyGenerator:  column: id  type: SNOWFLAKEtables:  dislike_action:    actualDataNodes: action-ds0.dislike_action_$->{0..7},action-ds1.dislike_action_$->{8..15}    databaseStrategy:      inline:        algorithmExpression: action-ds$->{Math.floorDiv((Math.abs(user_id.hashCode())  % 16) , 8)}        shardingColumn: user_id    logicTable: dislike_action    tableStrategy:      inline:        algorithmExpression: dislike_action_$->{Math.abs(user_id.hashCode()) % 16}        shardingColumn: user_id  like_action:    actualDataNodes: action-ds0.like_action_$->{0..7},action-ds1.like_action_$->{8..15}    databaseStrategy:      inline:        algorithmExpression: action-ds$->{Math.floorDiv((Math.abs(user_id.hashCode())  % 16) , 8)}        shardingColumn: user_id    logicTable: like_action    tableStrategy:      inline:        algorithmExpression: like_action_$->{Math.abs(user_id.hashCode())  % 16}        shardingColumn: user_id  dislike_target_count:    actualDataNodes: count-ds0.dislike_target_count_$->{0..7},count-ds1.dislike_target_count_$->{8..15}    databaseStrategy:      inline:        algorithmExpression: count-ds$->{Math.floorDiv((Math.abs(target_id.hashCode()) % 16), 8)}        shardingColumn: target_id    logicTable: dislike_target_count    tableStrategy:      inline:        algorithmExpression: dislike_target_count_$->{Math.abs(target_id.hashCode())  % 16}        shardingColumn: target_id  like_target_count:    actualDataNodes: count-ds0.like_target_count_$->{0..7},count-ds1.like_target_count_$->{8..15}    databaseStrategy:      inline:        algorithmExpression: count-ds$->{Math.floorDiv((Math.abs(target_id.hashCode()) % 16), 8)}        shardingColumn: target_id    logicTable: like_target_count    tableStrategy:      inline:        algorithmExpression: like_target_count_$->{Math.abs(target_id.hashCode()) % 16}        shardingColumn: target_id

在 Swagger UI 中操作点赞,控制台输出如下:M6K28资讯网——每日最新资讯28at.com

Logic SQL: select dislikeact0_.id as id1_0_, dislikeact0_.create_time as create_t2_0_, dislikeact0_.delete_time as delete_t3_0_, dislikeact0_.update_time as update_t4_0_, dislikeact0_.vsn as vsn5_0_, dislikeact0_.status as status6_0_, dislikeact0_.target_id as target_i7_0_, dislikeact0_.target_type as target_t8_0_, dislikeact0_.user_id as user_id9_0_ from dislike_action dislikeact0_ where dislikeact0_.user_id=? and dislikeact0_.target_type=?Actual SQL: action-ds0 ::: select dislikeact0_.id as id1_0_, dislikeact0_.create_time as create_t2_0_, dislikeact0_.delete_time as delete_t3_0_, dislikeact0_.update_time as update_t4_0_, dislikeact0_.vsn as vsn5_0_, dislikeact0_.status as status6_0_, dislikeact0_.target_id as target_i7_0_, dislikeact0_.target_type as target_t8_0_, dislikeact0_.user_id as user_id9_0_ from dislike_action_0 dislikeact0_ where dislikeact0_.user_id=? and dislikeact0_.target_type=? ::: [2707692781417059328, Test]

其中:M6K28资讯网——每日最新资讯28at.com

  • Logic SQL:逻辑 SQL 中的表为 dislike_action
  • Actual SQL:实际执行的 SQL 表为 dislike_action_0,数据库为 action-ds0

5. 项目信息

项目地址见:https://gitee.com/litao851025/lego/tree/master/services/likeM6K28资讯网——每日最新资讯28at.com

本文链接:http://www.28at.com/showinfo-26-15763-0.html基于 DDD 的互联网“赞&amp;踩”系统

声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com

上一篇: Spring 框架中Spring Cache缓存解决方案

下一篇: 微服务之负载均衡使用场景

标签:
  • 热门焦点
Top