sensitive-word-admin v1.3.0 釋出 如何支援敏感詞控臺分散式部署?

發表於2024-02-18

擴充閱讀

sensitive-word-admin v1.3.0 釋出 如何支援分散式部署?

sensitive-word-admin 敏感詞控臺 v1.2.0 版本開源

sensitive-word 基於 DFA 演算法實現的高效能敏感詞工具介紹

更多技術交流

view

業務背景

如果我們的敏感詞部署之後,不會變化,那麼其實不用考慮這個問題。

但是實際業務,敏感詞總是隨著時間不斷變化的,所以我們需要支援敏感詞的動態修改。

整體設計

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 敏感修改

3) 刪除

刪除 '敏感修改'

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 的模式。

開原始碼

sensitive-word-admin v1.3.0

參考資料

https://github.com/houbb/sensitive-word-admin

本文由部落格一文多發平臺 OpenWrite 釋出!

相關文章