拓展閱讀 sensitive-word-admin v1.3.0 發佈 如何支持分散式部署? sensitive-word-admin 敏感詞控台 v1.2.0 版本開源 sensitive-word 基於 DFA 演算法實現的高性能敏感詞工具介紹 更多技術交流 業務背景 如果我們的敏感詞部署之後,不 ...
拓展閱讀
業務背景
如果我們的敏感詞部署之後,不會變化,那麼其實不用考慮這個問題。
但是實際業務,敏感詞總是隨著時間不斷變化的,所以我們需要支持敏感詞的動態修改。
整體設計
pull vs push
以資料庫存儲自定義場景為例,如果頁面修改了敏感詞信息,那麼如何通知到部署的多台敏感詞客戶端呢?
一般通知方式有兩大類:
1)push 推送方式
修改時同時通知敏感詞發生了變化,每個敏感詞客戶端接收到通知後,重新初始化敏感詞信息。
優點是實時性比較高,缺點是需要引入額外的通知機制,需要通知的服務比較多時,也比較麻煩。
2)pull 拉取方式
修改後,直接落庫資料庫,每一個敏感詞客戶端自己定時拉取變更的信息。
這種方式有點是非常簡單,缺點是存在一定的延遲性。
考慮到我們的場景可以允許分鐘級的延遲,所以這裡先實現定時拉取方式。
如何知道敏感詞是否發生了變化?
定時拉取的方式比較簡單,但是每一次拉取的話,如何知道是否需要重新初始化呢?
雖然每次的初始化的耗時還好,但是考慮到變更不是很頻繁,所以有沒有辦法定時拉取時知道有沒有變化呢?
回顧一下上一篇文章,我們設計的 word 表
create table word
(
id int unsigned auto_increment comment '應用自增主鍵' primary key,
word varchar(128) not null comment '單詞',
type varchar(8) not null comment '類型',
status char(1) not null default 'S' comment '狀態',
remark varchar(64) not null comment '配置描述' default '',
operator_id varchar(64) not null default 'system' comment '操作員名稱',
create_time timestamp default CURRENT_TIMESTAMP not null comment '創建時間戳',
update_time timestamp default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新時間戳'
) comment '敏感詞表' ENGINE=Innodb default charset=UTF8 auto_increment=1;
create unique index uk_word on word (word) comment '唯一索引';
根據更新時間可以嗎?
如果我們所有的數據都不執行物理刪除,那麼直接根據 word 表的 update_time 即可判斷。
但是如果一個數據真的被刪除了,那麼這種方式就不行了。
delete 的數據怎麼辦?
如果我們期望執行物理刪除的話,那隻有添加對應的日誌表。
我們可以通過日誌表的 update_time 來處理。
操作日誌表
v1.2.0 的表設計
回顧一下 v1.2.0 表設計,如下:
create table word_log
(
id int unsigned auto_increment comment '應用自增主鍵' primary key,
batch_id varchar(128) not null comment '批次號',
word varchar(128) not null comment '單詞',
type varchar(8) not null comment '類型',
status char(1) not null default 'S' comment '單詞狀態。S:啟用;F:禁用',
remark varchar(64) not null comment '配置描述' default '',
operator_id varchar(64) not null default 'system' comment '操作員名稱',
create_time timestamp default CURRENT_TIMESTAMP not null comment '創建時間戳',
update_time timestamp default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新時間戳'
) comment '敏感詞操作日誌表' ENGINE=Innodb default charset=UTF8 auto_increment=1;
create index ix_word on word_log (word) comment '單詞普通索引';
create index ix_batch_id on word_log (batch_id) comment '批次號普通索引';
枚舉:
insert into lc_enum_mapping (table_name, column_name, `key`, label) values ('word_log', 'status', 'S', '正常');
insert into lc_enum_mapping (table_name, column_name, `key`, label) values ('word_log', 'status', 'F', '失效');
insert into lc_enum_mapping (table_name, column_name, `key`, label) values ('word_log', 'type', 'ALLOW', '允許');
insert into lc_enum_mapping (table_name, column_name, `key`, label) values ('word_log', 'type', 'DENY', '禁止');
表結構調整
我們對原來的表做一點調整。
調整後的建表語句
考慮到後續 sensitive-word 可能做精確的單個單詞變化處理,我們最好可以知道每一次詞內容的具體變化。
word 敏感詞主題
word_before 變更前的單詞
word_after 變更後的單詞
調整後的建表語句:
drop table word_log;
create table word_log
(
id int unsigned auto_increment comment '應用自增主鍵' primary key,
batch_id varchar(128) not null comment '批次號',
word varchar(128) not null comment '單詞',
word_before varchar(128) null comment '變更前單詞',
word_after varchar(128) null comment '變更後單詞',
type varchar(8) not null comment '類型',
status char(1) not null default 'S' comment '單詞狀態',
remark varchar(64) not null comment '配置描述' default '',
operator_type varchar(16) not null default '' comment '操作類別',
operator_id varchar(64) not null default 'system' comment '操作員名稱',
create_time timestamp default CURRENT_TIMESTAMP not null comment '創建時間戳',
update_time timestamp default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新時間戳'
) comment '敏感詞操作日誌表' ENGINE=Innodb default charset=UTF8 auto_increment=1;
create index ix_word on word_log (word) comment '單詞普通索引';
create index ix_batch_id on word_log (batch_id) comment '批次號普通索引';
create index ix_update_time on word_log (update_time) comment '更新時間普通索引';
添加操作類別(operator_type):
insert into lc_enum_mapping (table_name, column_name, `key`, label) values ('word_log', 'operator_type', 'CREATE', '新增');
insert into lc_enum_mapping (table_name, column_name, `key`, label) values ('word_log', 'operator_type', 'DELETE', '刪除');
insert into lc_enum_mapping (table_name, column_name, `key`, label) values ('word_log', 'operator_type', 'UPDATE', '更新');
例子
1)新增
新增 '敏感'
word 敏感
word_before null
word_after 敏感
2)修改
修改 '敏感',到 '敏感修改'
word 敏感
word_before 敏感
word_after 敏感修改
- 刪除
刪除 '敏感修改'
word 敏感修改
word_before 敏感修改
word_after null
刷新核心邏輯
我們啟動一個定時任務,判斷存在更新時,則重新初始化對應的敏感詞信息。
package com.github.houbb.sensitive.word.admin.web.config;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.baomidou.mybatisplus.mapper.Wrapper;
import com.github.houbb.heaven.util.util.DateUtil;
import com.github.houbb.sensitive.word.admin.dal.entity.WordLog;
import com.github.houbb.sensitive.word.admin.service.service.WordLogService;
import com.github.houbb.sensitive.word.bs.SensitiveWordBs;
import groovy.util.logging.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* 分散式部署的更新問題:
*
* 模式1:push
* 實時性好,但是需要感知系統的存在。
*
* 模式2:pull
* 存在延遲,但是無狀態,簡單。
*
* 這裡採用模式2
*
* @since 1.2.0
*/
@Component
@Slf4j
public class MySensitiveWordScheduleRefresh {
private static final Logger logger = LoggerFactory.getLogger(MySensitiveWordScheduleRefresh.class);
@Autowired
private SensitiveWordBs sensitiveWordBs;
@Autowired
private WordLogService wordLogService;
/**
* 刷新時間間隔
* @since 1.3.0
*/
@Value("${sensitive-word.refresh-interval-seconds}")
private int refreshIntervalSeconds;
@PostConstruct
public void init() {
logger.info("MySensitiveWordScheduleRefresh init with refreshIntervalSeconds={}", refreshIntervalSeconds);
// 單線程定時調度。
// TODO: 調整對應的 word_log 實現
ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
executorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
try {
logger.info("MySensitiveWordScheduleRefresh start");
refresh();
logger.info("MySensitiveWordScheduleRefresh end");
} catch (Exception e) {
logger.error("MySensitiveWordScheduleRefresh meet ex", e);
}
}
}, refreshIntervalSeconds, refreshIntervalSeconds, TimeUnit.SECONDS);
}
/**
* 更新詞庫
*
* 每次資料庫的信息發生變化之後,首先調用更新資料庫敏感詞庫的方法。
* 如果需要生效,則調用這個方法。
*
* 說明:重新初始化不影響舊的方法使用。初始化完成後,會以新的為準。
*/
private void refresh() {
// 延長10S,避免遺漏
int timeDiffer = refreshIntervalSeconds + 10;
// 判斷當前一段時間內是否存在變化?
Date date = DateUtil.addSecond(new Date(), -timeDiffer);
Wrapper<WordLog> wordLogWrapper = new EntityWrapper<>();
wordLogWrapper.gt("update_time", date);
int count = wordLogService.selectCount(wordLogWrapper);
if(count <= 0) {
logger.info("MySensitiveWordScheduleRefresh 沒有新增的變化信息,忽略更新。");
return;
}
// 每次資料庫的信息發生變化之後,首先調用更新資料庫敏感詞庫的方法,然後調用這個方法。
// 後續可以優化為針對變化的初始化。
sensitiveWordBs.init();
}
}
sensitive-word.refresh-interval-seconds
屬性指定了刷新的間隔,可配置。
小結
分散式環境下還是儘可能的追求架構的簡潔性,這裡只是一種實現的方式,也可以自己實現基於 push 的模式。
開源代碼
參考資料
https://github.com/houbb/sensitive-word-admin
本文由博客一文多發平臺 OpenWrite 發佈!