作者小头像 Lv.2
71 成长值

个人介绍

这个人很懒,什么都没有留下

感兴趣或擅长的领域

暂无数据
个人勋章
TA还没获得勋章~
成长雷达
50
21
0
0
0

个人资料

个人介绍

这个人很懒,什么都没有留下

感兴趣或擅长的领域

暂无数据

达成规则

发布时间 2026/10/22 10:07:42 最后回复 yd_219023509 2026/10/22 18:19:59 版块 智慧园区
25 6 0
发布时间 2026/10/21 10:07:49 最后回复 yd_219023509 2026/10/22 18:18:17 版块 智慧园区
23 7 0
他的回复:
补一个 Vue 3 响应式系统与观察者模式的对应关系。Vue 3 并不是直接让数据对象保存一组回调,而是维护 targetMap: WeakMap>:每个对象的每个属性对应一个 Dep,Dep 内部用 Set 保存读取该属性的副作用。Proxy 的 get 调用 track(target, key),把当前 activeEffect 加入该属性的 Dep;set 调用 trigger(target, key),取出对应副作用并交给调度器。这里 Dep 就是观察者集合,track/trigger 就是订阅和通知。工程上最关键的是每次副作用重新执行前先清理上一轮依赖。例如模板原本读取 cond ? state.a : state.b,后来只读取 state.b,如果仍保留对 state.a 的订阅,修改 state.a 就会触发无意义更新。组件卸载时还需要通过 scope.stop() 停止作用域内的副作用,否则会产生残留引用。简化示意:```jslet activeEffect;const targetMap = new WeakMap();function track(target, key) {  if (!activeEffect) return;  let depsMap = targetMap.get(target);  if (!depsMap) targetMap.set(target, depsMap = new Map());  let dep = depsMap.get(key);  if (!dep) depsMap.set(key, dep = new Set());  dep.add(activeEffect);  activeEffect.deps.push(dep);}function trigger(target, key) {  const dep = targetMap.get(target)?.get(key);  if (!dep) return;  for (const effect of [...dep]) {    effect.scheduler ? effect.scheduler() : effect.run();  }}```这个例子省略了嵌套副作用、暂停追踪和任务队列,但能清楚看出三件事:依赖表负责记录“谁读了什么”,副作用执行前要清理旧依赖,触发时遍历副本可以避免订阅集合在通知过程中被修改。
发布时间 2026/10/20 21:06:14 最后回复 yd_219023509 2026/10/22 18:14:22 版块 智慧园区
33 5 1
他的回复:
这类数据适合用 ClickHouse 做“明细日志 + 聚合分析”,但不建议把实时在场状态和频繁更新/删除也交给它。比较稳妥的拆分是:ClickHouse 存进出明细、Redis 或业务库存当前在场状态、物化视图存分钟/小时统计。1)明细表设计CREATE TABLE parking_gate_events(    event_id UUID,    event_time DateTime64(3, 'Asia/Shanghai'),    lot_id UInt32,    gate_id LowCardinality(String),    lane_id UInt16,    plate_hash FixedString(16),    event_type LowCardinality(String),    confidence Float32,    source LowCardinality(String),    version UInt64 DEFAULT toUnixTimestamp64Milli(event_time),    INDEX idx_plate_hash plate_hash TYPE bloom_filter(0.01) GRANULARITY 4)ENGINE = ReplacingMergeTree(version)PARTITION BY toYYYYMM(event_time)ORDER BY (lot_id, event_time, gate_id, plate_hash)TTL event_time + INTERVAL 24 MONTH;设计要点:- 车辆进出是典型的追加型事件,MergeTree 比行式数据库更适合;按车场和时间做排序键,能直接服务“某车场某时间段”的主查询。- 车牌号不要明文入库。建议统一车牌格式后做加盐 HMAC,取前 16 字节存成 plate_hash;需要展示原车牌时单独做加密、权限隔离和审计。- 月分区通常够用,不要按车牌分片或建过细分区,否则小 part 过多会拖慢合并和查询。数据量特别大时再评估按日分区。- 设备重试会产生重复事件。上游尽量生成唯一 event_id 并保证幂等;ReplacingMergeTree 只能做最终去重,不是强事务,关键去重仍要在接入层完成。2)写入- 单条 INSERT 的成本很高,建议按 5000~50000 行或每 1~2 秒攒批写入。- 可以用 Kafka/数据接入服务批量写 ClickHouse;HTTP 写入时开启 async_insert=1、wait_for_async_insert=0,并限制单批大小。- 入口道闸离线缓存的数据要按事件时间补写,同时监控设备时钟漂移,否则分区裁剪和轨迹顺序都会出错。3)常用查询查询某车在某车场的时间范围轨迹:SELECT event_time, gate_id, lane_id, event_typeFROM parking_gate_eventsWHERE lot_id = {lot_id:UInt32}  AND plate_hash = {plate_hash:FixedString(16)}  AND event_time >= {start:DateTime64(3)}  AND event_time {end:DateTime64(3)}ORDER BY event_time;查询每小时进出量:SELECT toStartOfHour(event_time) AS hour,       countIf(event_type = 'enter') AS enters,       countIf(event_type = 'exit')  AS exitsFROM parking_gate_eventsWHERE lot_id = {lot_id:UInt32}  AND event_time >= now() - INTERVAL 24 HOURGROUP BY hourORDER BY hour;做小时级看板时,建议再建 AggregatingMergeTree 物化视图预聚合 enters/exits 和 uniqExactState(plate_hash),看板只查聚合表,不要每次扫原始明细。4)当前在场数不是 ClickHouse 的强项直接对全量进出记录做 enter-exit 相减,遇到跨天停车会算错,而且成本随历史数据增长。应在 Redis/业务库维护以车场+plate_hash 为键的会话状态,入场置为 in、出场置为 out;ClickHouse 只保存会话快照和明细,用于事后审计与统计。5)性能与运维- 查询必须带 lot_id 和时间范围,避免 SELECT * 和全表扫描。- 车牌精确检索依赖 plate_hash 的 bloom filter;跨车场统计可加按 plate_hash、event_time 排序的 projection 或单独汇总表。- 开启 TTL,并定期检查 system.parts;历史明细转低频或冷存储。- 若要求“入场即查、出场即结算”的强一致状态,请放在 OLTP/Redis,ClickHouse 负责分析和追溯,不要让它承担高并发单行更新。
他的回复:
先把概念拆开:SBR 记录 SQL 语句,RBR 记录实际行变更,MIXED 不是固定比例混合,而是由 MySQL 按语句判断该用哪一种。真正要比较的是“谁来决定结果”和“从库需要做什么”。SBR 的优点是一次批量 UPDATE 影响一万行也只记一条 SQL,日志体积小、可读性好,适合受控的批处理和审计辅助;但前提是从库重放时能得到与主库完全相同的结果。NOW()/CURRENT_TIMESTAMP、RAND()、UUID()、用户变量、无 ORDER BY 的 LIMIT,以及依赖 SQL_MODE、时区、字符集、表结构或触发器的语句,都可能让两边走偏。主库上生成的 auto_increment 值在并发插入时也可能让 SBR 出现顺序差异。SBR 还会让从库重新执行查询和执行副作用,排查时不能只看“SQL 一样”就认为安全。RBR 记录主库已经生成的行镜像,从库按行应用,不重新求值 RAND() 等函数,因此对非确定性语句更稳。它的代价是批量 UPDATE/DELETE 的 binlog 会明显变大,可能增加网络、落盘和保留空间;表没有合适主键时,从库定位行可能变成全表扫描,复制延迟会被放大。binlog_row_image=FULL 信息完整但更大,MINIMAL 只记录定位列和变化列,能省日志,但要测试触发器、外键和下游 CDC 的兼容性。还要记住,RBR 只改变 DML 的记录方式,DDL 仍以语句记录,表结构漂移依然要治理。MIXED 的优点是折中,普通语句省日志,不安全语句切到 RBR;缺点是切换行为跟版本、SQL 模式和语句分类有关,排查时不能仅凭配置猜 binlog 里有什么。版本细节也很重要:MySQL 5.7.7 起默认已经是 ROW,MySQL 8.0 仍默认 ROW;说“MIXED 是 MySQL 默认”并不适用于当前主流版本。可用 SHOW WARNINGS 辅助发现不安全的语句,但不要把它当成跨版本保证。生产选型我会这样定:- InnoDB OLTP、主从一致性优先:binlog_format=ROW,先保证每张被复制的表有主键或唯一定位键。- 只在对日志量极端敏感、SQL 完全受控且已做长期一致性校验时,才考虑 SBR;不要把“日志小”当成唯一指标。- MIXED 主要用于兼容旧系统,不能替代应用层对非确定性写法的治理。- 出现复制异常时,用 mysqlbinlog 解码行事件核对主键、前后镜像和事务边界;RBR 下可以配合 binlog_row_image 和 binlog_row_metadata 优化存储与 CDC 解析。- 无论哪种格式,都要做 pt-table-checksum/业务抽样校验,并监控 binlog 增长、从库延迟和无主键表;复制格式不是数据一致性的唯一保险。
发布时间 2026/10/19 23:41:55 最后回复 yd_248437377 2026/10/21 12:56:41 版块 开发者空间
9 5 0
他的回复:
OBS 新手最容易犯的错,是把它当成“网盘”或本地文件系统:对象没有目录,只有 bucket + key;PUT 同名 key 默认是覆盖;它也不提供 POSIX 文件锁。按下面几类检查,能避开大部分线上事故。1. 区域、Endpoint 和桶名。桶名全局唯一,创建后区域通常不能随意迁移;SDK 里的 region、endpoint、桶名必须匹配,不要把其他云的 S3 endpoint 直接替换过来。OBS 与 S3 有兼容接口,但鉴权、错误码、扩展头和部分能力并不完全等价,跨云拷贝 SDK 代码时要逐项验证。2. 权限不要只看“能不能上传”。IAM 权限、桶策略、对象 ACL 可能是叠加和显式拒绝的关系,排查 403 时要同时看身份策略、资源策略、KMS 权限、签名时间和系统时间。前端绝对不要放永久 AK/SK,浏览器直传应使用临时凭证或预签名 URL;预签名 URL 要设置短有效期和最小权限,日志里也不要打印完整签名。公共读/公共写更要谨慎,开启后至少配合防盗链、来源限制和访问日志。3. 大文件必须按分段上传处理。不要自己切文件后并发 PUT 成多个普通对象;应使用 SDK 的 multipart upload,并实现失败重试、超时和 abort。中断的上传如果一直不 abort,会留下分片并持续占空间。普通上传的 ETag 常被当成 MD5,但分段上传或服务端加密后不一定等于整文件 MD5,完整性校验应以 SDK/控制台提供的 checksum/Content-MD5 机制为准。4. 重试要有幂等策略。上传成功但客户端超时的场景很常见,直接生成新 key 重试会产生重复对象;如果业务允许覆盖,可对同一 key 重试并校验 checksum,若不允许覆盖则先 HEAD/查询元数据或用唯一业务键。遇到 429、5xx 要指数退避并加抖动,不要无间隔死循环。5. 访问路径和费用要一起设计。同区域 ECS 访问 OBS 应优先走 VPC 终端节点/内网路径,避免无意走公网;外网下行、请求次数、低频/归档取回、最短存储期、跨区域复制都可能产生费用。开启版本控制后,删除通常只是加删除标记,旧版本和非当前版本仍会占容量;生命周期规则要同时考虑“转储、过期删除、清理非当前版本、清理未完成分片”。6. 不要假设对象存储有文件锁或原子重命名。多个实例同时写同一个 key 会相互覆盖;需要并发控制时使用唯一版本 key、数据库乐观锁、版本控制或业务侧锁,并明确最后一次写入的语义。对象 key 要由程序生成并做长度、字符和路径规范化,不要直接拼接用户输入,尤其要处理前导斜杠、 ..、URL 编码和超长名称。7. 浏览器直传要单独验证 CORS。除了 PUT 本身,预检 OPTIONS、允许的 Origin、Header 暴露和凭证模式都要配置正确;PostObject 表单策略还应限制 key 前缀、大小和 Content-Type。不要为了“先跑通”把 Origin 设成 * 又同时允许携带凭证。8. 安全和可观测性别等到出事才补。生产桶建议默认加密、关闭不必要的公共访问、开启访问日志和云审计,关键数据配置跨区域复制或备份;事件通知通常要按至少一次投递设计,消费者必须幂等。代码里要记录 request-id、bucket、key、状态码和耗时,排查问题时比只打一句“上传失败”有效得多。一句话:先把对象模型、权限边界、分片生命周期和费用路径想清楚,再写上传代码;OBS 的性能不是主要难点,权限、覆盖语义和残留资源才是新手最容易踩的坑。
发布时间 2026/10/20 10:25:37 最后回复 yd_219023509 2026/10/20 16:15:04 版块 开发者空间
21 6 0
他的回复:
这道题不能只背“先删还是先更”,因为只要数据库提交和缓存失效之间存在时间差,异步方案本质上就是最终一致。关键是看它把哪种并发窗口压小,以及能不能接受这个窗口。一、标准写法仍是 Cache-Aside:先提交数据库事务,再删除缓存。不要更新缓存,也不要先删缓存再写库。更新缓存会引入并发写覆盖,而且在数据库回滚时容易留下“数据库里不存在”的缓存值;先删缓存则把旧值回填窗口拉得更长。但“先更库、后删缓存”也有一个经典竞态:1. 读请求缓存未命中,从数据库读到旧值;2. 写请求提交新值并删除缓存;3. 读请求把刚才读到的旧值回填缓存。此后缓存可能一直脏,直到 TTL 到期或下一次写操作。删除缓存失败、事务提交前就删除缓存,都会放大这个问题。因此删除必须放在提交之后,并且要有重试或可靠补偿。二、延迟双删的代价:通常写成“更新库 -> 删除缓存 -> 延迟一段时间 -> 再删除一次”。它能清掉第一次删除后回填的旧值,但延迟多久只能通过读路径最大耗时、网络抖动、GC 停顿和回填延迟来估算,无法数学上保证覆盖所有竞态;延迟越短越可能漏,越长则脏窗口越长。第二次删除仍可能失败,还需要幂等、重试和监控。它适合作为成本较低的加固,不应被描述成强一致保证。三、Binlog/CDC 订阅的代价:把缓存失效从业务代码搬到数据变更事件,优点是能覆盖直接改库、批量脚本和绕过服务的写入,也便于重放和统一治理;缺点是链路更长,存在 binlog 保留、消费者 lag、重复消费、乱序、schema 变更和断点恢复问题。必须按主键或业务键保证同一 key 的失效顺序,消费端做幂等,并监控延迟。它仍然是“提交后再失效”,无法消除提交到失效之间的读旧值窗口,只是让这个窗口更可控、更可观测。四、版本号/时间戳的代价与收益:在同一数据库事务里递增 version,让缓存保存 value + version;回填时用 Lua 或条件写,只在 version 更大时覆盖,能直接拒绝陈旧回填,通常比延迟双删更确定。成本是每个缓存值多一份元数据、一次比较写入,版本必须由数据库生成且单调,不能用容易回拨的机器时间;还要处理缓存中没有版本、版本丢失和批量迁移。它也只能控制“旧值不能覆盖新值”,如果业务要求提交后立刻所有节点都读到新值,就不能只靠异步缓存,关键读要走主库或绕过缓存,或者引入读屏障/租约等更强协议。工程上更稳妥的组合是:事务提交后删缓存,删除失败进事务性 outbox 或重试队列;用 CDC 做异步兜底;缓存带合理 TTL;热点 key 加互斥回填或 singleflight。这样能保证最终一致并限制故障影响,但不要把它宣传成严格线性一致;强一致场景应让数据库成为唯一权威,别让缓存既承担性能又承担事实来源。
发布时间 2026/10/18 16:30:24 最后回复 yd_229207802 2026/10/22 22:43:20 版块 数据库
14 4 0
他的回复:
Online DDL 的核心不是“DDL 完全不锁表”,而是把耗时工作放到允许 DML 继续执行的阶段,并把必须独占元数据的窗口压到最短。执行过程可以理解为:1. 准备阶段短暂获取 MDL,检查表定义并选择 COPY、INPLACE 或 INSTANT 算法;2. 执行阶段持有共享 MDL,后台构建索引或重建数据,同时把并发 INSERT/UPDATE/DELETE 记录到 row log;3. 提交阶段再申请 MDL 排他锁,停止新 DML、回放增量、切换表定义,然后释放锁。最后一步虽然通常很短,但排队中的请求仍可能在这时短暂停顿。这里最容易混淆的是三个概念:- ALGORITHM=COPY:复制到临时新表,期间通常不能并发写入,最保守也最占空间。- ALGORITHM=INPLACE:避免 COPY 整表,但可能重建聚簇索引或产生大量临时文件;“INPLACE”并不自动等于 LOCK=NONE。- ALGORITHM=INSTANT:只改数据字典或利用行版本,通常最快,但版本和操作限制最严格。实际执行时应显式写成 ALGORITHM=INPLACE, LOCK=NONE(或 ALGORITHM=INSTANT),让 MySQL 在无法满足要求时直接报错,而不是按默认策略降级。最终能不能在线还取决于 MySQL 版本、存储引擎、列类型、索引类型和表选项,不能只背一张固定表。常见的相对安全操作包括普通二级索引的 ADD/DROP/RENAME INDEX、表或列注释、默认值、表/列重命名、部分 VARCHAR 长度扩展等;MySQL 8.0.12 起支持 INSTANT ADD COLUMN,8.0.29 起支持 INSTANT DROP COLUMN,但都有列位置、行格式、全文索引等限制,升级版本后也要验证。修改字符集/排序规则、大量列类型转换、列顺序调整、存储引擎或行格式变更、复杂的表分区变更,通常会走 COPY 或高成本重建,需要按停机窗口处理。生产上还要防三类“看起来在线、实际把业务拖垮”的问题:第一是 MDL 雪崩。长事务不提交会占着共享 MDL,DDL 等不到排他锁,后续查询又排在 DDL 后面。执行前应检查 information_schema.innodb_trx、performance_schema.metadata_locks 和 sys.schema_table_lock_waits,并设置合理的 lock_wait_timeout;不要在有长事务时硬上。第二是资源放大。即使允许并发 DML,重建索引仍会消耗大量磁盘 I/O、临时空间和 redo,并产生 purge 压力。innodb_online_alter_log_max_size 太小时,高并发写入可能让 row log 溢出并导致 DDL 回滚;建议低峰执行并监控空间、IO、复制延迟和失败后的回滚成本。第三是复制链路。主库 DDL 完成不代表从库立刻追平,大表回放可能持续很久;多级复制还会放大延迟。应在同版本影子库预演,评估 gh-ost、pt-online-schema-change 的触发器、外键、生成列、切换和回滚限制,而不是默认认为第三方工具一定更安全。一句话总结:选 INSTANT/INPLACE 时用 ALGORITHM 和 LOCK 明确约束,先检查长事务和版本矩阵;做不到 LOCK=NONE 的超大表,应按变更计划使用在线迁移工具,而不是在业务高峰直接执行 ALTER。
他的回复:
两者真正要区分的是“表数据和索引占用的页归谁管理”,而不只是“一个文件还是多个文件”。共享表空间由 innodb_data_file_path 定义,多个表的数据页、索引页以及部分系统结构都放在 ibdata 文件里;独立表空间在 innodb_file_per_table=ON 时,给每张新建表一个自己的 .ibd 文件,里面仍然包含该表的聚簇索引、二级索引和行数据,所以它不是“每个索引一个文件”。该参数的主要作用是把新建普通表的空间分配边界切到独立的表空间上;对已经存在的表不会自动搬迁,修改参数后通常要通过重建表或 ALTER TABLE ... TABLESPACE 迁移,具体语法和是否在线要按 MySQL 版本确认。工程上的关键差别主要有四点:1. 空间回收:独立表空间执行 DROP/TRUNCATE 后可以删除或快速回收对应 .ibd,碎片也可以重建回收;共享表空间只能留下可复用的空闲页,ibdata 往往不会自动缩小,想真正收缩通常要导出重建。注意 OPTIMIZE TABLE 会重建表,大表上要评估磁盘、MDL 和复制延迟,不能把它当日常清理命令。2. 管理粒度:独立表空间便于单表迁移、跨实例传输(需要版本、页大小、加密等条件匹配)、按表放到不同目录或存储策略;代价是文件句柄和文件系统元数据更多,表数量很大时要关注 inode、打开文件数和备份扫描开销。3. 并非所有“系统数据”都由这个参数决定:change buffer 仍在系统表空间;undo 在较新版本有独立 undo 表空间;MySQL 8.0 的数据字典在 mysql.ibd,doublewrite 也有独立的 dblwr 文件,因此不能简单说“关掉 file-per-table 所有东西都进 ibdata1”,也不能指望打开它就把 undo、redo、binlog 一起拆开。4. 备份与容量规划:全量物理备份通常两种都能处理,但独立表空间更适合单表恢复和空间治理;共享表空间文件少、部署简单,却容易出现单文件持续膨胀和恢复粒度过粗。实际生产中还要同时监控 ibdata、undo、临时表空间、redo 和 binlog,不能只看 .ibd 大小。结论:默认保持 innodb_file_per_table=ON,符合现代 MySQL 的空间回收和运维习惯。共享表空间不是“不能用”,而是更适合对文件数量敏感、又能接受整体容量管理的场景;切换前先确认版本差异,并在低峰期用可回滚的重建方案处理存量表。
发布时间 2026/10/20 15:59:08 最后回复 didala 2026/10/22 15:24:02 版块 开发者空间
18 4 0
他的回复:
HashMap 解决哈希冲突的核心是“链地址法”:数组每个槽位放的不是单个键值对,而是可以挂多条记录的桶;Java 8 起,桶内过长时还会把链表转成红黑树。一、先算桶下标HashMap 不直接拿 hashCode() % length,而是先做扰动,再与容量掩码按位与:static final int hash(Object key) {    int h;    return key == null ? 0 : (h = key.hashCode()) ^ (h >>> 16);}int index = (n - 1) & hash; // n 为表的容量,必须是 2 的幂把高 16 位异或到低 16 位,是为了让高位也参与桶选择,降低大量 key 低位相同造成的碰撞概率。容量保持为 2 的幂,则 (n - 1) & hash 能等价于取模,同时比除法更便宜。null key 固定落在 0 号桶。二、发生碰撞后怎么放1. 如果新 key 与桶内已有节点满足 hash 相同且 key.equals(oldKey) 为 true,说明是同一个逻辑键,直接覆盖 value,并返回旧值。2. 如果不 equals,就以链地址法挂在同一个桶里。JDK 7 采用头插,JDK 8 改为尾插,查找时沿链表比较 hash 和 equals。3. JDK 8 中,链表长度达到 8,且数组容量至少为 64 时,会树化为红黑树;容量不足 64 时优先扩容,而不是树化。删除后树节点减少到 6 时会退化为链表,阈值留出缓冲,避免频繁来回转换。因此平均查找复杂度是 O(1);极端碰撞时,链表退化为 O(n),红黑树则是 O(log n)。例如 "Aa" 和 "BB" 的 hashCode 都是 2112,它们可能进入同一个桶,但 equals 为 false,所以会并存而不是覆盖。三、扩容与碰撞的关系当元素数量超过 threshold = capacity * loadFactor 时扩容,容量翻倍,并把原节点按 oldCap 这一位分成 low/high 两条链重新落到新数组。扩容能缩短桶链,但它仍是有成本的,所以能在创建时估算容量就不要让它反复扩容。粗估公式可用:期望元素数 / 0.75 + 1,再向上取到 2 的幂。四、实践中的注意事项- hashCode 与 equals 必须保持契约:equals 相等的对象,hashCode 必须相等;建议一起重写。- 参与 hashCode/equals 的字段不要在对象作为 key 放入后又修改,否则桶下标会变,原来的值可能“找不到”。- 恶意构造大量同 hash 且不可比较的 key 仍会制造碰撞;JDK 8 的树化能缓解但不应把它当安全边界,外部输入应先做边界与规模控制。- 高并发读写不要使用 HashMap,改用 ConcurrentHashMap;需要稳定迭代顺序可用 LinkedHashMap 或 TreeMap。一句话总结:HashMap 用“扰动哈希 + 2 的幂掩码定位桶 + 链表/红黑树链地址法”处理冲突,再配合扩容缩短桶链;它解决的是普通哈希碰撞,不会自动解决糟糕的 hashCode 实现或并发写入问题。