基于 DDD 的互聯(lián)網(wǎng)“贊&踩”系統(tǒng)
該文是系統(tǒng)的用戶手冊,主要體現(xiàn)系統(tǒng)功能和所支持的高級特性,關于系統(tǒng)的核心設計正在整理中,請稍安勿躁
1. 背景
隨著社交媒體的普及,用戶生成的內(nèi)容數(shù)量急劇增加。為了幫助用戶更好地發(fā)現(xiàn)和分享內(nèi)容,許多社交媒體平臺都提供了贊/踩服務。
2. 目標
贊/踩服務是一種用戶反饋機制。隨著社交媒體的普及和發(fā)展,人們越來越喜歡在一種平臺上分享自己的觀點和生活,這時就需要一種形式化的反饋機制來快速評價這些信息的好壞。贊/踩服務的目標是為了提高用戶互動性,增加內(nèi)容的社會影響力,從而增加活躍用戶數(shù)量。
3. 快速入門
系統(tǒng)所涉及的功能包括:
功能 | 描述 |
贊/踩 | 用戶可以點擊對應的贊或踩按鈕,以表達自己的喜好或不喜好 |
取消贊/踩 | 用戶可以取消之前的贊/踩,以更正自己的想法 |
計數(shù)器 | 贊和踩的數(shù)量都需要計數(shù)器,用于顯示文章或評論的受歡迎程度和社交影響力等 |
贊/踩歷史 | 用戶可以查看自己贊/踩的歷史記錄,以查看自己對文章或評論的態(tài)度 |
3.1. 開發(fā)環(huán)境
基于 Spring Boot 框架進行開發(fā),以 DDD 作為業(yè)務邏輯承載模型。
框架 | 版本 | 依賴說明 |
JDK | 1.8+ | 運行環(huán)境 |
Spring Boot | 2.3.12.RELEASE | |
Spring Data | 2.3.9.RELEASE | 基于 JPA 實現(xiàn)持久化;基于 Redis 完成緩存加速(可選) |
Lego | 0.1.22 | DDD 模型落地 |
springfox | 3.0.0 | 文檔管理 |
RocketMQ | 2.2.1 | 領域事件,異步處理(可選) |
Sharding Sphere | 4.4.1 | 分庫分表(可選) |
3.2. 模塊介紹
該項目使用標準的 “六邊形架構”,對業(yè)務和技術進行分離,所以模塊較多,但層次更為清晰。
模塊 | 作用 |
domain | 核心邏輯層,DDD 中核心組件,包括實體、值對象、聚合根、領域服務等 |
app | 應用服務層,DDD 中的應用服務,主要負責流程編排 |
infrastructure | 基礎設施層,主要負責與 DB 或其他服務進行通訊 |
api | RPC 服務中的接口定義,被 FeignClient 和 FeignService 依賴 |
FeignService | api 中接口的實際實現(xiàn)者,完成接口的適配 |
FeignClient | api 中Proxy實現(xiàn)者,方便使用方直接調(diào)用 |
bootstrap | 應用啟動入口,包括 Spring Boot 入口和所有配置 |
3.3. 啟動項目
3.3.1. 建庫建表
建表語句在infrastructure/src/main/resources/sql 目標下,包括單庫和分庫分表配置。單庫建表語句如下:
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. 修改數(shù)據(jù)庫配置
修改bootstrap/src/main/resource/application.yml 增加數(shù)據(jù)配置,具體如下:
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/
當看到如下界面證明程序啟動成功:
3.3. 核心 API
核心接口如下:
功能 | 請求地址 | 參數(shù)類型 | 參數(shù)說明 | 返回結果 | cur Demo |
點贊 | POST /feignService/action/command/like | RequestBody | {"userId": 用戶id, "targetType": 目標對象類型,"targetId": 目標對象 id} | 無 | 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}" |
取消點贊 | POST /feignService/action/command/unlike | RequestBody | {"userId": 用戶id, "targetType": 目標對象類型,"targetId": 目標對象 id} | 無 | 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}" |
獲取點贊數(shù)量 | GET /feignService/targetCount/query/getLikeCountByTarget | RequestParam | type:目標類型;ids:目標id集合 | [{"targetType":目標對象類型,“targetId":目標對象id,"count":點贊數(shù)量}] | curl -X GET "http://127.0.0.1:8080/feignService/targetCount/query/getLikeCountByTarget?type=test&ids=1" -H "accept: /" |
獲取點贊記錄 | GET /feignService/action/query/getLikeByUserAndType | RequestParam | type:目標類型;userId:userId | [{"targetType":目標對象類型,“targetId":目標對象id,"userId":用戶id,"valid":是否有效}] | curl -X GET "http://127.0.0.1:8080/feignService/action/query/getLikeByUserAndType?userId=2&type=test" -H "accept: /" |
踩 | POST /feignService/action/command/dislike | RequestBody | {"userId": 用戶id, "targetType": 目標對象類型,"targetId": 目標對象 id} | 無 | 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}" |
取消踩 | POST /feignService/action/command/unDislike | RequestBody | {"userId": 用戶id, "targetType": 目標對象類型,"targetId": 目標對象 id} | 無 | 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}" |
獲取踩數(shù)量 | GET /feignService/targetCount/query/getDislikeCountByType | RequestParam | type:目標類型;ids:目標id集合 | [{"targetType":目標對象類型,“targetId":目標對象id,"count":點贊數(shù)量}] | curl -X GET "http://127.0.0.1:8080/feignService/targetCount/query/getDislikeCountByType?type=test&ids=1" -H "accept: /" |
獲取點贊記錄 | GET /feignService/action/query/getDislikeByUserAndType | RequestParam | type:目標類型;userId:userId | [{"targetType":目標對象類型,“targetId":目標對象id,"userId":用戶id,"valid":是否有效}] | curl -X GET "http://127.0.0.1:8080/feignService/action/query/getDislikeByUserAndType?userId=2&type=test" -H "accept: /" |
核心API直接在 Swagger UI 上進行測試即可?。。?/p>
4. 高級特性
4.1. 自定義業(yè)務驗證
流程中涉及兩個重要的概念:
- ActionUser: 操作 贊或踩 的用戶;
- ActionTarget: 贊或踩 的目的對象;
在實際業(yè)務場景,需要對這兩個對象的有效性進行驗證,比如:
- ActionUser
用戶是否存在?
用戶狀態(tài)是否有效?
是否是黑名單用戶?
- ActionTarget
- 目標對象是否存在?
- 目標對象是否已經(jīng)下線/禁用?
這些功能擴展直接實現(xiàn)對應的 Loader 即可。
4.1.1. ActionUser 擴展
ActionUser 定義如下:
public class ActionUser {
@Column(name = "user_id", updatable = false)
private Long userId;
@Transient
private boolean valid;
}
如果用戶狀態(tài)存在問題,直接將 valid 置為 false 即可。
ActionUserLoader 定義如下:
public interface ActionUserLoader {
ActionUser loadByUserId(Long userId);
}
只需實現(xiàn) ActionUserLoader 并注冊為 Spring 托管 Bean 即可,具體如下:
@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。
【備注】Bean 必須注冊為LoadActionUserByUserId.BEAN_NAME(actionUserLoader),否則框架將無法識別。
4.1.2. ActionTarget 擴展
ActionTarget 定義如下:
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;
}
如果目標對象狀態(tài)存在問題,直接將 valid 置為 false 即可。
由于系統(tǒng)中可以存在多種目標對象,為每個類型提供單獨的 Loader,接口如下:
public interface SingleActionTargetLoader {
/**
* 是否支持 type 類型的 Target
* @param type
* @return
*/
boolean support(String type);
/**
* 加載 Target 對象
* @param type
* @param id
* @return
*/
ActionTarget load(String type, Long id);
}
按需要實現(xiàn)接口,樣例如下:
@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);
}
}
}
該實現(xiàn)對 type 為 Test 的 Target 進行加載。
4.2. 發(fā)布領域事件
領域事件是 DDD 中的重要概念,當系統(tǒng)發(fā)生狀態(tài)變化后,將變化結果對外進行廣播,從而實現(xiàn)系統(tǒng)間的集成。
4.2.1. 添加 RocketMQ
外部領域事件通過 RocketMQ 向外廣播,需要搭建 RocketMQ 集群并在項目中增加 RocketMQ 的支持。
在 bootstrap 模塊的 pom 中增加 rocketmq starter,具體如下:
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
</dependency>
在 application.yml 增加 rocketmq 的配置,具體如下:
rocketmq:
name-server: http://127.0.0.1:9876
producer:
group: like-service
至此,便完成了與 rocketmq 的集成。
4.2.2. 打開領域事件開關
在 application.yml 添加如下配置:
like:
event:
#開啟領域事件
enable: true
#指定領域事件發(fā)送的 topic
topic: like-event-topic
開啟領域事件,并指定事件發(fā)送的 topic
4.2.3. 測試領域事件
重新啟動項目,當控制臺輸出以下表明配置成功:
Use RocketMQ to Publish Like Event
使用 swagger 運行 dislike 操作,從日志中可知消息發(fā)送成功:
4.2.4. 支持領域事件
系統(tǒng)支持的領域事件包括:
領域事件類型 | 觸發(fā)機制 | tag | 消息體 |
LikeMarkedEvent | 點贊成功 | LikeMarkedEvent | 見 LikeMarkedEvent 類 |
LikeCancelledEvent | 取消點贊成功 | LikeCancelledEvent | 見 LikeCancelledEvent 類 |
DislikeMarkedEvent | 踩成功 | DislikeMarkedEvent | 見 DislikeMarkedEvent 類 |
DislikeCancelledEvent | 取消踩成功 | DislikeCancelledEvent | 見 DislikeCancelledEvent 類 |
4.3. 緩存加速
在系統(tǒng)中,獲取目標對象的 贊/踩 數(shù)量接口調(diào)用量最大,會成為系統(tǒng)的第一個性能卡點,針對這個問題,可以通過引入 redis 緩存進行性能加速。
4.3.1. 添加 redis 依賴
首先需要引入 redis 相關依賴,在 bootstrap 的 pom 中增加如下配置:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
然后在 application.yml 中增加 redis 相關配置:
spring:
redis:
host: 127.0.0.1
port: 6379
4.3.2. 開啟緩存
完成redis配置后,需要在 application.yml 開啟對應的緩存,具體如下:
target:
count:
dislike:
cache:
# 是否開啟緩存
enable: true
like:
cache:
# 是否開啟緩存
enable: true
4.3.3. 緩存效果
未開啟緩存前,每次查詢數(shù)量都會執(zhí)行一條 sql,具體如下:curl 命令如下:
curl -X GET "http://127.0.0.1:8080/feignService/targetCount/query/getDislikeCountByType?type=test&ids=1" -H "accept: */*"
輸出 sql 如下:
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 (?))
開啟緩存后,再次執(zhí)行以上 curl,控制臺不會輸出sql,而是會輸出一行日志:
c.g.l.i.s.RedisBasedTargetCountCache : load All Data From Cache for test and [1]
說明緩存已經(jīng)生效。
所有的 action 操作都會與同步的對緩存進行更新。
4.4. 異步存儲
當目標對象出現(xiàn)熱點時,會產(chǎn)生高并發(fā)請求,對于 action 來說,主要以數(shù)據(jù)插入和數(shù)據(jù)的分散更新為主。但對于 count,就會產(chǎn)生熱點更新,從而成為系統(tǒng)的瓶頸。在這個場景,最適合的解決方案便是引入 MQ 對流量進行削峰填谷。
4.4.1. 增加 rocketmq
與 4.2.1. 添加 RocketMQ 內(nèi)容一致,在此不再重復。
4.4.2. 開啟異步化
在 application.yml 中增加如下配置:
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 中執(zhí)行 點贊 操作,從控制臺可以看到如下日志:
[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
從日志上看,可以得出:
- nio 線程向 MQ 發(fā)送消息
- MessageThread 線程從 MQ 中獲取數(shù)據(jù)并執(zhí)行 incr 操作
4.5. 分庫分表
隨著系統(tǒng)的運行,數(shù)據(jù)量會逐漸增大,最終超出單個 DB 的容量上限。這種情況下,最佳實踐便是對數(shù)據(jù)庫進行分庫分表。
4.5.1. 構建數(shù)據(jù)庫和表
在 infrastructure 模塊的 sql 目錄下存在兩個 sql 文件:
- create_table_sharding_action.sql : 贊/踩 操作分庫分表
- create_table_sharding_count.sql : 贊/踩 計數(shù)分庫分表
示例中總共分16張表,存放在兩個數(shù)據(jù)庫中:
- db1 存放 0-7 表
- db2 存放 8-15 表
如圖所示:
4.5.2. 添加 ShardingSphere 支持
在 bootstrap 的pom 文件增加 ShardingSphere 的依賴,具體如下:
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
</dependency>
其次,增加分庫分表配置文件,為了方便新建 application.properties 存放分庫分表配置:
# 數(shù)據(jù)源配置
# 總共4個數(shù)據(jù)源
spring.shardingsphere.datasource.names=action-ds0, action-ds1, count-ds0, count-ds1
# action-ds0 數(shù)據(jù)源配置
spring.shardingsphere.datasource.action-ds0.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.action-ds0.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.action-ds0.jdbc-url=jdbc:mysql://127.0.0.1:3306/like_action_0?allowPublicKeyRetrieval=true&useSSL=false&serverTimezone=UTC
spring.shardingsphere.datasource.action-ds0.username=root
spring.shardingsphere.datasource.action-ds0.password=root
# action-ds1 數(shù)據(jù)源配置
spring.shardingsphere.datasource.action-ds1.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.action-ds1.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.action-ds1.jdbc-url=jdbc:mysql://127.0.0.1:3306/like_action_1?allowPublicKeyRetrieval=true&useSSL=false&serverTimezone=UTC
spring.shardingsphere.datasource.action-ds1.username=root
spring.shardingsphere.datasource.action-ds1.password=root
# count-ds0 數(shù)據(jù)源配置
spring.shardingsphere.datasource.count-ds0.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.count-ds0.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.count-ds0.jdbc-url=jdbc:mysql://127.0.0.1:3306/like_count_0?allowPublicKeyRetrieval=true&useSSL=false&serverTimezone=UTC
spring.shardingsphere.datasource.count-ds0.username=root
spring.shardingsphere.datasource.count-ds0.password=root
# count-ds1 數(shù)據(jù)源配置
spring.shardingsphere.datasource.count-ds1.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.count-ds1.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.count-ds1.jdbc-url=jdbc:mysql://127.0.0.1:3306/like_count_1?allowPublicKeyRetrieval=true&useSSL=false&serverTimezone=UTC
spring.shardingsphere.datasource.count-ds1.username=root
spring.shardingsphere.datasource.count-ds1.password=root
# 分庫分表規(guī)則配置
# 使用雪花算法生成分布式主鍵id的值
spring.shardingsphere.sharding.default-key-generator.column=id
spring.shardingsphere.sharding.default-key-generator.column-type=BIGINT
spring.shardingsphere.sharding.default-key-generator.type=SNOWFLAKE
spring.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
# 根據(jù) 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
# 根據(jù) 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_id
spring.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_id
spring.shardingsphere.sharding.tables.like_action.database-strategy.inline.algorithm-expression=action-ds$->{Math.floorDiv((Math.abs(user_id.hashCode()) % 16) , 8)}
# 計數(shù)表配置
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
# 根據(jù) 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
# 根據(jù) 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_id
spring.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_id
spring.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
在雪花算法情況下,尾數(shù)會變的極度不均勻,所以在進行計算之前,通常先執(zhí)行 hashCode 在進行取模操作。
4.5.3. 分庫分表效果
啟動應用程序,控制臺輸出 sharding 相關配置,具體如下:
defaultKeyGenerator:
column: id
type: SNOWFLAKE
tables:
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 中操作點贊,控制臺輸出如下:
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]
其中:
- Logic SQL:邏輯 SQL 中的表為 dislike_action
- Actual SQL:實際執(zhí)行的 SQL 表為 dislike_action_0,數(shù)據(jù)庫為 action-ds0
5. 項目信息
項目地址見:https://gitee.com/litao851025/lego/tree/master/services/like