首页 文章 精选 留言 我的

精选列表

搜索[单体架构],共10000篇文章
优秀的个人博客,低调大师

HBase架构解析

Hbase组件  客户端Client 整个HBase集群的入口 使用HBase RPC机制与HMaster和HRegionserver通信 与HMaster通信进行管理类的操作 与HRegionserver通信进行读写类操作 包含访问HBase的接口,并维护cache来加快对HBase的访问,与HRegionserver交互 程序协调服务Zookeeper 保证任何时候,集群中只有一个Master 存贮所有Region的寻址入口 实时监控Region server的上线和下线信息。并实时通知给Master 存储HBase的schema和table元数据 HBase主节点Master 管理用户对Table的增删改查操作 管理HRegionServer的负载均衡,调整Region分布 在Region Split后,负责新Region的分配 在HRegionServer停机后,负责失效HRegionServer上的Region迁移 HMaster失效仅会导致所有元数据无法被修改,表的数据读写还是可以正常运行 HBase与Zookeeper HBase元数据存储在Zookeeper中 默认情况下,HBase管理Zookeeper示例,比如,启动或停止Zookeeper Zookeeper解决HBase单节点故障问题 HMaster与HRegionserver启动时回向Zookeeper注册 寻找RegionServer过程详解  - Zookeeper(读取Zookeeper找到-ROOT-表的位置) - -ROOT-(-ROOT-表包含.META.表所在的region列表,该表只会有一个Region;Zookeeper中记录了-ROOT-表的location) - .META(这个表包含所有的用户空间region列表,已经RegionServer的服务器地址) - 用户表 - Client第一次操作后,会将-ROOT-和.META.缓存到本地,不需要再访问zookeeper (PS:0.96之后的版本,ZK不再存储ROOT表信息,直接存储META表信息) HBase容错性 Master容错:Zookeeper重新选择一个新的Master 无Master过程中,数据读取仍然照常进行; 无Master中,region切分,负载均衡无法进行; RegionServer容错:定时向Zookeeper汇报心跳,如果一段时间内未出现心跳,master将该RegioinServer上的Region重新分配到其他RegionServer上;失效服务器上“预写”日志由服务器进行分割并派送给新的ReginServer zookeeper容错:Zookeeper高可靠的服务,不存在单点故障

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

YARN-架构

标签(空格分隔): 大数据 YARN [toc] 原文:http://hadoop.apache.org/docs/r2.6.4/hadoop-yarn/hadoop-yarn-site/YARN.html Hadoop下一代MapReduce(YARN) MapReduce在hadoop-0.23中经历了完整的革新,也就是引入了MapReduce 2.0(MRv2),俗称YARN。 MRv2的核心思想是将原来JobTracker的两个主要功能拆分,也就是将资源管理(resource management)和调度与监控(scheduling/monitoring)分离为两个daemon进程。其思想表现为持有一个全局的ResourceManager(RM)以及每个应用有一个ApplicationMaster(AM)。一个应用对应一个传统的MR

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

HDFS的架构

主从结构 主节点,只有一个: namenode 从节点,有很多个: datanodes 在版本1中,主节点只有一个,在 版本2中主节点有两个。 namenode 负责(管理): 接收用户操作请求 维护文件系统的目录结构 管理文件与 block 之间的关系, block 与 datanode 之间关系 block 是 文件分成的 块, hdfs 中默认的 block 块的大小是64M ,实际的文件肯定会分很多块存储。 datanode 负责(存储): 存储文件 文件被分成 block 存储在磁盘上 为了保证数据安全,文件会有多个副本 在MapReduce 执行计算任务的时候,计算任务的节点。 Hadoop 在存储文件的时候,文件会有多个副本。 因为 Hadoop 是分布式的存储与计算平台,他使用的是廉价的服务器,廉价的服务器可能会发生故障,如果发生故障了,那么数据就不能用了, hadoop 为了提高系统的安全性,对数据进行了冗余存储, 默认情况下数据的副本 是 3. 比如 10G的电影,实际在HDFS中存放的大小不是10G ,而是 30G 。 而在具体数据访问的时候,namenode 会根据不同的 datanode 具体的状况,这种状况包括 他的存储空间、繁忙情况、内存等等从三个副本中选出一个副本去使用 , 另外两个副本就是空闲状态。当这个副本出现问题的时候就会切换到另外一个副本。当然切换回去之后,这个出问题的副本可以得到恢复(与其他副本同步)。 从客户端(java、命令端、其他语言开发的终端….)提交的数据到 namenode , namenode 这时就会看整个集群中的 datanode 中谁属于空闲,谁可用,都会有自己的算法 (关于算法可以从源码的方向上进行理解), 然后确定把数据存储在哪几台机器上,然后 这些客户端就会直接和这些 datanodes打交道,把数据直接交给具体的数据节点。当然数据节点在存储数据的过程中,肯定会有一些日志信息(管理与block之间的关系、block与datanode之间的关系),每一次集群启动的时候,datanode都会把元数据信息(关于 datanode内部怎么组织的)报告给 namenode, 这样namenode 才能知道 datanode 具体是怎么组织的 然后才能够管理。开始做,坚持做,重复做

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

MapReduce 的架构

主从结构 主节点,只有一个 : JobTracker ,JobTracker 一般情况下,运行在 namenode 这台机器上。 从节点,有很多个 : TaskTrackers , 部署在剩下的 datanode 上。 这里谈的 JobTracker 、TaskTrackers 都是 JVM。 JobTracker 负责: 接收客户提交的计算任务 计算任务: 肯定是一段代码---你要怎么操作我的数据。 把计算任务分给TaskTrackers 执行 监控TaskTracker的执行情况 我把任务分配给你了,能否分配成功?失败怎么办?工作的进度?工作的结果? 如果出现了这些问题,MapReduce都会透明的帮我们解决掉这些问题。假如被分配到任务的 TaskTracker 不能够 顺利的完成任务,JobTracker会透明的把任务切换到 另外一台虚拟机上。 但是这种 切换 我们是感觉不到的,这就是用框架的好处,我们写 java 的人只需要关注 业务逻辑。 TaskTrackers 负责 : 执行JobTracker分配的计算任务 开始做,坚持做,重复做

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

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

## 一、账户系统出问题,是真正的灾难 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日终对账 □ 余额 = 流水汇总 □ 异常自动告警 □ 安全审计 □ 所有操作有日志 □ 敏感操作有审批 □ 数据变更有记录 ``` --- ## 八、总结 账户系统核心要点: | 要点 | 说明 | |------|------| | 原子性 | 余额更新和流水记录同一事务 | | 一致性 | 余额 = 流水汇总,每日对账 | | 幂等性 | 业务订单号去重 | | 可追溯 | 所有操作有记录 | | 安全性 | 敏感操作有审批 | **血的教训:** > 资金安全是底线。账户系统出问题,不是丢数据,是丢信任。 --- *个人观点,仅供参考*

资源下载

更多资源
Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Spring

Spring

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

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

Sublime Text

Sublime Text

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

用户登录
用户注册