首页 文章 精选 留言 我的

精选列表

搜索[磐石架构],共10005篇文章
优秀的个人博客,低调大师

MCollective架构篇1-MCollective架构的引入

零基础学习Puppet自动化配置管理系列文档 Marionette Collective(MCollective)是一个与Puppet关系密切的服务运行框架。Puppet擅长管理系统的状态,但agent默认的30 分钟间隔的运行方式使它不合适作为实时管理控制工具使用,而MCollective的功能定位正式面向大规模主机群的实时任务并行处理。它离线消息中间件 技术实现检点间的信息传递,大量主机可以基于自身的某些固有属性(元数据)而非主机名进行分组,这意味着用这些信息按照不同标准将集群分为多个群组,任务 执行的目标是一个群组,而不是一台主机。 也可以参考纸飞机的博客关于mcollective的介绍http://junqili.com/ MCollective特点: 能够与小到大型服务器集***互 使用广播范式(broadcastparadigm)来进行请求分发,所有服务器会同时收到请求,而只有与请求所附带的过滤器匹配的服务器才会去执行这些请求。没有中心数据库来进行同步,网络是唯一的真理 打破了以往用主机名作为身份验证手段的复杂命名规则。使用每台机器自身提供的丰富的目标数据来定位它们。目标数据来自于:Puppet,Chef,Facter,Ohai或者自身提供的插件 使用命令行调用远程代理 能够写自定义的设备报告 大量的代理来管理包,服务和其他来自于社区的通用组件 允许写SimpleRPC风格的代理、客户端和使用Ruby实现WebUIs 外部可插件化(pluggable)实现本地需求 中间件系统已有丰富的身份验证和授权模型,利用这些作为控制的第一道防线。 重用中间件来做集群、路由和网络隔离以实现安全和可扩展安装。 MCollective 就是一个框架,一个空壳。它除了 MCO 命令之外都可以被替换被自定义。 备注:更多信息请参考http://docs.puppetlabs.com/ Middleware(RabbitMQ、ActiveMQ)介绍 RabbitMQ是一个实现了高级消息排队协议(AMQP)的消息队列服务。RabbitMQ基于OTP(Open Telecom Platform,开发电信平台)进行构建,并使用Erlang语言和运行时环境来实现。 ActiveMQ 是Apache出品,最流行的,能力强劲的开源消息总线。ActiveMQ 是一个完全支持JMS1.1和J2EE 1.4规范的 JMS Provider实现 备注:MCollective是基于Apache ActiveMQ中间件来进行开发和测试的,然而其对java和XML格式的配置文件的依赖使我们将更多的注意力和兴趣转移到RabbitMQ中间件服务 上。如果考虑到性能和扩展性,部署ActivemMQ是一个更好的选择。 工作原理图 mcollective触发更新图 本文转自凌激冰51CTO博客,原文链接:http://blog.51cto.com/dreamfire/1566371,如需转载请自行联系原作者

优秀的个人博客,低调大师

【架构实战】账户系统架构:资金安全是底线

## 一、账户系统出问题,是真正的灾难 2020年,我们支付系统出现一个严重Bug: - 用户A充值100元 - 系统记录:账户余额+100,但流水记录失败 - 用户A查询余额显示100元 - 但后台对账发现异常:账户余额 ≠ 流水汇总 **更严重的问题:** - 用户B提现100元 - 系统扣减余额成功,但提现记录失败 - 用户B余额显示正确,但钱没到账 **影响:** - 用户投诉 - 资金损失 - 监管风险 - 团队追责 从那以后,我们把账户系统作为最核心的系统来设计,**资金安全是底线**。 --- ## 二、账户系统核心模型 ### 2.1 账户模型 ``` ┌─────────────────────────────────────────────────────────────────┐ │ 账户系统核心模型 │ │ │ │ 用户账户(Account) │ │ ├── 账户ID │ │ ├── 用户ID │ │ ├── 账户类型(余额账户、积分账户、押金账户) │ │ ├── 余额 │ │ ├── 冻结金额 │ │ └── 版本号(乐观锁) │ │ │ │ 账户流水(Transaction) │ │ ├── 流水ID │ │ ├── 账户ID │ │ ├── 业务订单号 │ │ ├── 交易类型(充值、提现、消费、退款) │ │ ├── 交易金额 │ │ ├── 交易前余额 │ │ ├── 交易后余额 │ │ └── 交易时间 │ │ │ └──────────────────────────────────────────────────────────────────┘ ``` ### 2.2 账户表设计 ```sql -- 账户主表 CREATE TABLE account ( id BIGINT PRIMARY KEY AUTO_INCREMENT, account_no VARCHAR(32) NOT NULL COMMENT '账户号', user_id BIGINT NOT NULL COMMENT '用户ID', account_type TINYINT NOT NULL COMMENT '账户类型:1余额 2积分 3押金', balance DECIMAL(12,2) NOT NULL DEFAULT 0 COMMENT '余额', frozen_amount DECIMAL(12,2) NOT NULL DEFAULT 0 COMMENT '冻结金额', version INT NOT NULL DEFAULT 0 COMMENT '版本号', status TINYINT NOT NULL DEFAULT 1 COMMENT '状态:1正常 2冻结 3销户', create_time DATETIME NOT NULL, update_time DATETIME NOT NULL, UNIQUE KEY uk_account_no (account_no), UNIQUE KEY uk_user_type (user_id, account_type), INDEX idx_user_id (user_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='账户主表'; -- 账户流水表 CREATE TABLE account_transaction ( id BIGINT PRIMARY KEY AUTO_INCREMENT, transaction_no VARCHAR(64) NOT NULL COMMENT '流水号', account_no VARCHAR(32) NOT NULL COMMENT '账户号', biz_order_no VARCHAR(64) COMMENT '业务订单号', transaction_type TINYINT NOT NULL COMMENT '交易类型', amount DECIMAL(12,2) NOT NULL COMMENT '交易金额(正数入账,负数出账)', balance_before DECIMAL(12,2) NOT NULL COMMENT '交易前余额', balance_after DECIMAL(12,2) NOT NULL COMMENT '交易后余额', remark VARCHAR(255) COMMENT '备注', create_time DATETIME NOT NULL, UNIQUE KEY uk_transaction_no (transaction_no), INDEX idx_account_no (account_no), INDEX idx_biz_order_no (biz_order_no), INDEX idx_create_time (create_time) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='账户流水表'; ``` --- ## 三、账户核心操作 ### 3.1 充值流程 ```java /** * 充值服务 */ @Service @Slf4j public class RechargeService { @Autowired private AccountMapper accountMapper; @Autowired private TransactionMapper transactionMapper; @Transactional(rollbackFor = Exception.class) public RechargeResult recharge(RechargeRequest request) { // 1. 参数校验 validateRequest(request); // 2. 幂等性检查 if (transactionMapper.existsByBizOrderNo(request.getBizOrderNo())) { return RechargeResult.success("重复充值,已处理"); } // 3. 查询账户 Account account = accountMapper.selectByUserIdAndType( request.getUserId(), AccountType.BALANCE ); if (account == null) { throw new BusinessException("账户不存在"); } // 4. 乐观锁更新余额 BigDecimal balanceBefore = account.getBalance(); BigDecimal balanceAfter = balanceBefore.add(request.getAmount()); int rows = accountMapper.updateBalance( account.getId(), balanceAfter, account.getVersion() ); if (rows == 0) { throw new BusinessException("余额更新失败,请重试"); } // 5. 记录流水 Transaction transaction = new Transaction(); transaction.setTransactionNo(generateTransactionNo()); transaction.setAccountNo(account.getAccountNo()); transaction.setBizOrderNo(request.getBizOrderNo()); transaction.setTransactionType(TransactionType.RECHARGE); transaction.setAmount(request.getAmount()); transaction.setBalanceBefore(balanceBefore); transaction.setBalanceAfter(balanceAfter); transaction.setRemark("用户充值"); transactionMapper.insert(transaction); // 6. 发送充值成功事件 eventPublisher.publish(new RechargeSuccessEvent( request.getUserId(), request.getAmount() )); return RechargeResult.success(transaction.getTransactionNo()); } } ``` ### 3.2 提现流程 ```java /** * 提现服务 */ @Service @Slf4j public class WithdrawService { @Autowired private AccountService accountService; @Autowired private TransactionMapper transactionMapper; @Autowired private WithdrawOrderMapper withdrawOrderMapper; @Transactional(rollbackFor = Exception.class) public WithdrawResult withdraw(WithdrawRequest request) { // 1. 参数校验 validateRequest(request); // 2. 查询账户 Account account = accountService.getAccount( request.getUserId(), AccountType.BALANCE ); // 3. 检查余额 if (account.getBalance().compareTo(request.getAmount()) = #{amount} AND status = 1 """) int freezeAmount( @Param("accountNo") String accountNo, @Param("amount") BigDecimal amount ); } ``` ### 4.2 流水记录完整性 ```java /** * 账户流水服务 */ @Service @Slf4j public class TransactionService { /** * 记录交易流水(本地事务内) */ public void recordTransaction( String accountNo, String bizOrderNo, Integer transactionType, BigDecimal amount ) { // 1. 查询账户当前余额 Account account = accountMapper.selectByAccountNo(accountNo); // 2. 计算交易前后余额 BigDecimal balanceBefore = account.getBalance(); BigDecimal balanceAfter = balanceBefore.add(amount); // 3. 记录流水 Transaction transaction = new Transaction(); transaction.setTransactionNo(generateTransactionNo()); transaction.setAccountNo(accountNo); transaction.setBizOrderNo(bizOrderNo); transaction.setTransactionType(transactionType); transaction.setAmount(amount); transaction.setBalanceBefore(balanceBefore); transaction.setBalanceAfter(balanceAfter); transactionMapper.insert(transaction); log.info("记录流水: transactionNo={}, accountNo={}, amount={}", transaction.getTransactionNo(), accountNo, amount); } /** * 生成流水号(全局唯一) */ private String generateTransactionNo() { // 格式:日期(8位) + 机器ID(4位) + 序列号(12位) String date = DateUtil.format(new Date(), "yyyyMMdd"); String machineId = String.format("%04d", machineIdProvider.getMachineId()); String sequence = String.format("%012d", sequenceGenerator.next()); return date + machineId + sequence; } } ``` ### 4.3 账务一致性校验 ```java /** * 账务一致性校验服务 */ @Service @Slf4j public class AccountReconcileService { @Autowired private AccountMapper accountMapper; @Autowired private TransactionMapper transactionMapper; /** * 账务一致性校验 */ @Scheduled(cron = "0 0 2 * * ?") // 每天凌晨2点执行 public void reconcile() { log.info("开始账务一致性校验..."); // 1. 查询所有账户 List accounts = accountMapper.selectAll(); for (Account account : accounts) { // 2. 计算流水汇总 BigDecimal sumAmount = transactionMapper.sumAmountByAccountNo( account.getAccountNo() ); // 3. 对比账户余额和流水汇总 if (account.getBalance().compareTo(sumAmount) != 0) { log.error("账务不一致: accountNo={}, balance={}, sumAmount={}", account.getAccountNo(), account.getBalance(), sumAmount); // 4. 记录异常,人工处理 reconcileErrorMapper.insert(new ReconcileError( account.getAccountNo(), account.getBalance(), sumAmount, account.getBalance().subtract(sumAmount) )); // 5. 发送告警 alertService.sendAlert("账务异常", String.format("账户%s余额不一致,差额%.2f", account.getAccountNo(), account.getBalance().subtract(sumAmount))); } } log.info("账务一致性校验完成"); } /** * 账户余额修复(需要审批) */ @Transactional(rollbackFor = Exception.class) public void fixAccountBalance(String accountNo, BigDecimal correctBalance) { Account account = accountMapper.selectByAccountNo(accountNo); // 记录调整流水 BigDecimal adjustAmount = correctBalance.subtract(account.getBalance()); Transaction transaction = new Transaction(); transaction.setTransactionNo(generateTransactionNo()); transaction.setAccountNo(accountNo); transaction.setTransactionType(TransactionType.ADJUST); transaction.setAmount(adjustAmount); transaction.setBalanceBefore(account.getBalance()); transaction.setBalanceAfter(correctBalance); transaction.setRemark("账务修复"); transactionMapper.insert(transaction); // 更新账户余额 accountMapper.updateBalance(account.getId(), correctBalance, account.getVersion()); } } ``` --- ## 五、分布式事务方案 ### 5.1 TCC事务方案 ```java /** * 账户TCC事务 */ @Service public class AccountTccService { /** * Try:冻结余额 */ public boolean tryFreeze(String accountNo, BigDecimal amount) { return accountMapper.freezeAmount(accountNo, amount) > 0; } /** * Confirm:扣减冻结余额 */ public boolean confirmDeduct(String accountNo, BigDecimal amount) { return accountMapper.deductFrozenAmount(accountNo, amount) > 0; } /** * Cancel:解冻余额 */ public boolean cancelFreeze(String accountNo, BigDecimal amount) { return accountMapper.unfreezeAmount(accountNo, amount) > 0; } } /** * 支付TCC事务协调器 */ @Service public class PaymentTccCoordinator { @Autowired private AccountTccService accountTccService; @Autowired private OrderTccService orderTccService; @GlobalTransactional public void pay(Long userId, Long orderId, BigDecimal amount) { // Try阶段 boolean accountTry = accountTccService.tryFreeze(userId, amount); boolean orderTry = orderTccService.tryPay(orderId, amount); if (!accountTry || !orderTry) { throw new BusinessException("Try阶段失败"); } // Confirm阶段(Seata自动提交) // 如果失败,自动执行Cancel } } ``` ### 5.2 本地消息表方案 ```java /** * 本地消息表方案 */ @Service @Slf4j public class LocalMessageService { @Autowired private AccountMapper accountMapper; @Autowired private MessageMapper messageMapper; /** * 充值(本地事务) */ @Transactional(rollbackFor = Exception.class) public void recharge(RechargeRequest request) { // 1. 更新账户余额 accountMapper.updateBalance(...); // 2. 记录流水 transactionMapper.insert(...); // 3. 写入本地消息表(同一事务) LocalMessage message = new LocalMessage(); message.setMessageId(UUID.randomUUID().toString()); message.setTopic("recharge-success"); message.setMessageBody(JSON.toJSONString(request)); message.setStatus(MessageStatus.PENDING); messageMapper.insert(message); } /** * 消息发送任务 */ @Scheduled(fixedDelay = 5000) public void sendMessage() { List messages = messageMapper.selectPending(); for (LocalMessage message : messages) { try { // 发送MQ消息 rocketMQTemplate.send(message.getTopic(), message.getMessageBody()); // 更新状态为已发送 messageMapper.updateStatus(message.getMessageId(), MessageStatus.SENT); } catch (Exception e) { log.error("消息发送失败: {}", message.getMessageId(), e); } } } } ``` --- ## 六、踩坑实录 ### 坑1:余额更新丢失 > **问题**:并发更新余额时,后提交的请求覆盖前一个请求。 **踩坑场景**: - 用户余额100元 - 同时发起两个请求:充值50元、消费30元 - 理想结果:余额120元 - 实际结果:余额可能为80元或150元 **解决方案**:使用乐观锁 ```java @Update(""" UPDATE account SET balance = balance + #{delta}, version = version + 1 WHERE id = #{id} AND version = #{version} """) int updateBalanceWithVersion( @Param("id") Long id, @Param("delta") BigDecimal delta, @Param("version") Integer version ); ``` ### 坑2:流水记录缺失 > **问题**:余额更新成功,但流水记录失败。 **踩坑场景**: 数据库连接池满,流水插入失败,但余额已更新。 **解决方案**:本地事务 + 本地消息表 ```java @Transactional(rollbackFor = Exception.class) public void recharge(RechargeRequest request) { // 同一事务内,要么都成功,要么都失败 accountMapper.updateBalance(...); transactionMapper.insert(...); } ``` ### 坑3:幂等性缺失 > **问题**:同一请求重复处理,导致余额重复增加。 **踩坑场景**: 用户充值请求超时重试,系统处理了两次。 **解决方案**:业务订单号去重 ```java public void recharge(RechargeRequest request) { // 检查是否已处理 if (transactionMapper.existsByBizOrderNo(request.getBizOrderNo())) { return; // 已处理,直接返回 } // 处理充值 ... } ``` ### 坑4:账户并发热点 > **问题**:热门账户(如平台账户)并发量大,成为热点。 **踩坑场景**: 所有用户消费都要扣减平台账户余额,该账户成为热点。 **解决方案**:热点账户分片 ```java // 平台账户分片:account_0, account_1, ..., account_9 String accountNo = "platform_" + (userId % 10); // 查询时汇总 BigDecimal totalBalance = accountMapper.sumBalance("platform_*"); ``` ### 坑5:对账数据量大 > **问题**:历史流水数据量大,对账性能差。 **解决方案**:T+N对账 + 分区表 ```sql -- 流水表按月分区 CREATE TABLE account_transaction_202401 ( ... ) ENGINE=InnoDB; -- 只对最近7天数据对账 SELECT * FROM account_transaction WHERE create_time >= DATE_SUB(CURDATE(), INTERVAL 7 DAY); ``` --- ## 七、最佳实践 ### 7.1 账户系统安全检查清单 ``` 账户系统安全检查清单: □ 余额更新 □ 使用乐观锁或悲观锁 □ 本地事务保证原子性 □ 流水记录在同一事务 □ 流水记录 □ 每笔交易都有流水 □ 流水记录余额变化前后值 □ 流水号全局唯一 □ 幂等性 □ 业务订单号去重 □ 流水号唯一索引 □ 对账机制 □ T+1日终对账 □ 余额 = 流水汇总 □ 异常自动告警 □ 安全审计 □ 所有操作有日志 □ 敏感操作有审批 □ 数据变更有记录 ``` --- ## 八、总结 账户系统核心要点: | 要点 | 说明 | |------|------| | 原子性 | 余额更新和流水记录同一事务 | | 一致性 | 余额 = 流水汇总,每日对账 | | 幂等性 | 业务订单号去重 | | 可追溯 | 所有操作有记录 | | 安全性 | 敏感操作有审批 | **血的教训:** > 资金安全是底线。账户系统出问题,不是丢数据,是丢信任。 --- *个人观点,仅供参考*

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

用户登录
用户注册