1. 程式人生 > >成就係統實現(三)-架構設計

成就係統實現(三)-架構設計

1.技術選型

之前上網找過一些關於成就係統設計的文件,推薦的都是事件驅動模型,因此底層設計也基於這個模型來做,由業務方產生可能觸發成就的事件,統一放到一個佇列裡面,由執行緒去取佇列裡面的事件,進行分發處理。

技術點:

1.1 事件驅動模型

1.2 disruptor 號稱百萬流量的一個高併發處理框架

2.資料庫設計

2.1 成就包配置表 CREATE TABLE `achievement_package` (   `achievement_package_id` varchar(32) NOT NULL COMMENT '主鍵id',
  `name` varchar(32) NOT NULL COMMENT '包名稱',
  `start_time` bigint(20) NOT NULL COMMENT '開始時間',
  `end_time` bigint(20) NOT NULL COMMENT '結束時間',
  `create_time` bigint(20) NOT NULL COMMENT '建立時間',
  `op_time` bigint(20) NOT NULL COMMENT '更新時間',
  `last_ver` int(11) NOT NULL COMMENT '版本號',
  `is_valid` tinyint(4) NOT NULL COMMENT '是否有效 0-失效 1-有效 ',
  `name_code` varchar(32) NOT NULL DEFAULT '' COMMENT '成就包名國際化編碼',
  PRIMARY KEY (`achievement_package_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='成就包配置表'
2.2 成就模板表 CREATE TABLE `achievement_template` (   `achievement_template_id` varchar(32) NOT NULL COMMENT '主鍵id',
  `name` varchar(32) NOT NULL COMMENT '成就名稱',
  `name_code` varchar(32) NOT NULL COMMENT '成就名稱國際化編碼',
  `context` varchar(64) DEFAULT NULL COMMENT '描述',
  `context_code` varchar(64) DEFAULT NULL COMMENT '描述國際化編碼',
  `reward` varchar(64) NOT NULL COMMENT '獎勵 json',
  `pic_url` varchar(255) DEFAULT NULL COMMENT '成就圖片 status-url',
  `conditions` text NOT NULL COMMENT '達成條件 json',
  `achievement_package_id` varchar(32) NOT NULL COMMENT '所屬成就包',
  `start_time` bigint(20) NOT NULL COMMENT '成就開始時間',
  `end_time` bigint(20) NOT NULL COMMENT '成就結束時間',
  `create_time` bigint(20) NOT NULL COMMENT '建立時間',
  `op_time` bigint(20) NOT NULL COMMENT '更新時間',
  `last_ver` int(11) NOT NULL COMMENT '版本號',
  `is_valid` tinyint(4) NOT NULL COMMENT '是否有效 0-失效 1-有效 ',
  PRIMARY KEY (`achievement_template_id`),
  KEY `udx_ach_pack_id` (`achievement_package_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='成就模板表'  2.3 成就記錄表 CREATE TABLE `achievement_record` (
  `achievement_record_id` varchar(32) NOT NULL COMMENT '主鍵id',
  `customer_register_id` varchar(32) DEFAULT NULL COMMENT '小二id',
  `achievement_template_id` varchar(32) NOT NULL COMMENT '成就id',
  `fire_seed` int(11) NOT NULL COMMENT '火種數',
  `finish_time` bigint(20) NOT NULL COMMENT '完成時間',
  `status` smallint(6) NOT NULL COMMENT '狀態 0-未開始 1-進行中 2-已完成 3-進行中已過期 4-未完成已過期',
  `schedule` text COMMENT '進度',
  `create_time` bigint(20) NOT NULL COMMENT '建立時間',
  `op_time` bigint(20) NOT NULL COMMENT '更新時間',
  `last_ver` int(11) NOT NULL COMMENT '版本號',
  `is_valid` tinyint(4) NOT NULL COMMENT '是否有效 0-失效 1-有效 ',
  `achievement_package_id` varchar(32) NOT NULL COMMENT '成就包Id',
  PRIMARY KEY (`achievement_record_id`),
  UNIQUE KEY `udx_cus_ach_id` (`customer_register_id`,`achievement_template_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='使用者成就記錄表'

3.主要類圖


主要類講解: 1.AchievementEvent (成就事件基類)
 /**
     * 店鋪Id
     */
    private String entityId;
    /**
     * 使用者Id集合 完成事件的人
     */
    private List<String> customerRegisterIds;
    /**
     * 事件來源 1-APP 2-H5 {@link AchievementConstant.EventSource}
     */
    private int source;
    /**
     * 事件型別 see{@link AchievementEnum}
     */
    private int type;
    /**
     * 成就Id (封裝每個handler裡面對應的成就Id)
     */
    private String achievementId;
    /**
     * 佇列名
     */
    private String disruptorName;
由基類派生出多個成就事件子類

2.EventClient (事件分發器)
public class EventClient implements IEventClient, ApplicationContextAware {

    private ApplicationContext applicationContext;

    /**
     * 獲得上下文
     *
     * @param applicationContext
     * @throws BeansException
     */
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    /**
     * 釋出事件
     *
     * @param event
     * @return
     */
    public Boolean publish(AchievementEvent event) {
        String eventName = StringUtils.uncapitalize(event.getClass().getSimpleName());
        IEventPublisher publisher = (IEventPublisher) applicationContext.getBean(eventName + "Publisher");
        if (publisher != null) {
            return publisher.publish(event);
        }
        return false;
    }
}
這個類的作用將對應的事件分發給對應的事件釋出器處理 3.IEventPublisher(事件釋出)
public interface IEventPublisher<T extends AchievementEvent> {
    /**
     * 釋出事件
     * @param event
     * @return
     */
    boolean publish(T event);
}
每種型別的事件都有各自的釋出器,實現這個介面 舉例:OrderEventPublisher(訂單事件釋出器)
public class OrderEventPublisher implements InitializingBean, IEventPublisher<OrderEvent> {

    private static final EventTranslatorOneArg<OrderEvent, OrderEvent> translator = (OrderEvent event, long sequence,
                                                                                     OrderEvent arg0) -> {
        event.setDisruptorName(arg0.getDisruptorName());
        event.setEntityId(arg0.getEntityId());
        event.setCustomerRegisterIds(arg0.getCustomerRegisterIds());
        event.setOrderId(arg0.getOrderId());
        event.setAction(arg0.getAction());
        event.setOrderFee(arg0.getOrderFee());
        event.setType(arg0.getType());
        event.setSource(arg0.getSource());
        event.setOrderType(arg0.getOrderType());
    };
    private static Logger log = LoggerFactory.getLogger(OrderEventPublisher.class);
    private Disruptor<OrderEvent> disruptor;

    @Resource
    private WelcomeHandler welcomeHandler;
    @Resource
    private LoveProverbsHandler loveProverbsHandler;
    @Resource
    private TechnologyHouseHandler technologyHouseHandler;
    @Resource
    private CookeryGodPassHandler cookeryGodPassHandler;

    private int bufferSize;
    private RingBuffer ringBuffer;


    @Override
    public void afterPropertiesSet() throws Exception {
        disruptor = new Disruptor<>(() -> new OrderEvent(),
                4096, new EventHandlerThreadFactory("OrderEvent"), ProducerType.MULTI, new BlockingWaitStrategy());
        disruptor.handleEventsWith(welcomeHandler, loveProverbsHandler, technologyHouseHandler).then(cookeryGodPassHandler);
        disruptor.setDefaultExceptionHandler(new AchieveEventExceptionHandler("OrderEventDisruptor"));
        ringBuffer = disruptor.start();
        bufferSize = ringBuffer.getBufferSize();
    }

    @Override
    public boolean publish(OrderEvent event) {
        event.setDisruptorName("OrderEventDisruptor");
        if (ringBuffer.remainingCapacity() < bufferSize * 0.01) {
            log.warn("OrderEventDisruptor size = " + ringBuffer.remainingCapacity());
        }
        return ringBuffer.tryPublishEvent(translator, event);//釋出事件;
    }
}
每個事件釋出器管理自己的一個disruptor佇列,存放對應的事件,在初始化的時候設定事件的監聽器,也就是對應的成就處理類 之後的publish就呼叫ringbuffer的方法,有興趣的可以研究下disruptor原始碼 4.AbstractAchieveHandler(成就處理基類)
/**
     * 執行事件
     *
     * @param event
     * @param sequence
     * @param endOfBatch
     * @throws Exception
     */
    @Override
    public void onEvent(AchievementEvent event, long sequence, boolean endOfBatch) throws Exception {
        Logger.warn("{} {}  handle" +
                " event event = {}, 位於佇列序號={}", event.getDisruptorName(), this.getClass().getSimpleName(), event.toString(), sequence);
        if (event == null || CollectionUtils.isEmpty(event.getCustomerRegisterIds())) {
            LoggerUtil.error(FireMemberLoggerFactory.ACHIEVEMENT_LOGGER, LogMarker.ACHIEVEMENT_HANDLER,
                    "valid param fail event={}", event != null ? event.toString() : null);
            return;
        }
        if (!checkEvent(event)) {
            return;
        }
        for (String customerRegisterId : event.getCustomerRegisterIds()) {
            AchievementDTO achievementDTO = checkAchievement(customerRegisterId, event);
            if (achievementDTO.getStatus() == AchievementConstant.RecordStatus.STATUS_COMPLETED && reward(achievementDTO)) {
                sendMessage(achievementDTO);
            }
        }
    }
主要採用了模板設計模式,大部分功能在基類裡面定義好了,各自具體的成就類只需要繼承基類,實現checkEvent方法就行 舉例:WelComeHandler
public class WelcomeHandler extends AbstractAchieveHandler {

    private static String achievementId = "c5a968a3372c474794ef670fe9ad7132";

    @Override
    protected boolean checkEvent(AchievementEvent event) {
        //1.事件型別檢驗
        if (event instanceof OrderEvent) {
            OrderEvent orderEvent = (OrderEvent) event;
            if (orderEvent.getAction() == AchievementEnum.OrderActionEnum.SUBMIT.getCode() &&
                    AchievementConstant.OrderType.ORDER_SHOP.equals(orderEvent.getOrderType())) {
                event.setAchievementId(achievementId);
                return true;
            }
        }
        return false;
    }
}
checkEvent方法主要是判斷成就是否達成,因為每個成就都有各自的條件,因此可變的部分由各自的子類去實現。 就此,大致的一個框架設計 就出來了。