AI 时代个人的一些思考(持续更新)

技术一定是向着降低技术的门槛发展的,比如AI coding,降低了写代码的门槛,但也其实只是降低写代码这件事情本身,很多系统性的思考,系统设计的思想,AI是不能脑控你的,你得要自己理解why。 AI时期泡沫已经来了,不确定是不是泡沫的最高点,但是泡沫终会散去,怎么在这个时期保全自己? 苦练基本功 把自己的能力与AI红利摘除,你不能是因为吃了AI红利而强,与AI强绑定的话,可能就完蛋了。 这一点其实也和上面绑定,也就是一致。 初稿:2026-04-25。若你读到与当下实践不一致的句子,以你现场约束为准;欢迎留言或 PR 指出可改进之处。

2026年4月25日 · 1 分钟

MySQL 与 Elasticsearch 双写一致性

场景引入 如果你设计了一个类似 B 站的视频网站中的搜索系统,或许在初期 MySQL 的 like 模糊搜已经能满足你对视频的标题、简介的搜索需求。 SELECT * FROM videos WHERE title LIKE '%'||:search_term||'%' OR description LIKE '%'||:search_term||'%'; 如果后期系统视频的量级上来了,对性能有一定要求了,你或许已经开始考虑 ES 了。后面如果你还想让与搜索关键词相关的创作者名称、视频评论、关联标签也能级联视频搜出来,甚至弄一个各种业务维度的融合公式,那么 ES 这样的搜索引擎再适合不过了! 但是,如果数据写一份 MySQL,还要落一份 ES 来支撑筛选/搜索,要怎么写入两个异构的存储系统、并保证它们的一致性呢? Note 如果你有更好的场景欢迎提出/补充~ 方案一:同步双写 优点 简单粗暴 ES 同步写入快 缺点 业务耦合,扩展性差,代码侵入性强(要在写 MySQL 的地方后写 ES 的代码,要在代码设计上做好收敛) 影响性能,写入两个存储,响应时间变长(本来 MySQL 的写入性能不是很高,再加一个 ES,系统的性能必然会下降),吞吐也会下降 存在双写失败丢数据风险(比如写完MySQL,ES 就挂了) 方案二:异步双写 优点 性能高 不易出现数据丢失问题,主要基于 MQ 消息的消费保障机制,比如 ES 宕机或者写入失败,还能重新消费 MQ 消息 多源写入之间相互隔离(业务侧只管发消息),解耦数据生产者和消费者,便于扩展更多的数据源写入 缺点 同样存在业务耦合的问题,写完 DB 之后,还需要发个 MQ(代码设计上做好收敛,也可以关注一下所用 orm 框架有没有 hook 之类的能力,支持你“写后 xxx”) 接入新的数据源需要实现新的消费者代码 系统复杂度增加,引入了消息中间件 MQ是异步消费模型,存在延迟问题 Tip MQ 可以缓冲和 DB 的负载能力不一致问题 ...

2025年11月17日 · 1 分钟

Redis 大 key 问题

大 key 标准 在一般的业务场景下(并发和容量要求都不大): 单个string的value>1MB 容器ds元素数量超过10000 在高并发且低延迟的场景中:(互联网上看到比较多的版本) 单个string的value>10KB 容器ds元素数量超过5000或整体value>10MB 阿里云的云版本Redis: Key本身的数据量过大:一个String类型的Key,它的值为5MB Key中的成员数过多:一个ZSET类型的Key,它的成员数量为10000个 Key中成员的数据量过大:一个Hash类型的Key,它的成员数量虽然只有1000个但这些成员的Value(值)总大小为100MB 但其实Redis大key问题的定义及评判准则并非一成不变,没有固定的判别标准,还是要根据Redis在实际业务中的使用来综合评估。(注意边界问题) 大 key 影响 读取成本高 时延(执行命令时间更长) 带宽(大 key 消耗更多的带宽,从而影响相关服务) 大key的写操作容易阻塞,从而导致无法正常响应 慢查询 主从同步异常 占更多的存储空间,从而导致逐出,甚至是 OOM(Out Of Memory) 集群架构下,某个数据分片的内存使用率远超其他数据分片,即数据分片的内存资源不均衡 根本原因 Redis 是单线程! 大 key 产生原因 业务设计不合理。这是最常见的原因,不经过合理拆分,就直接把大json塞在一个key中;甚至塞二进制文件数据。 没有处理好value的动态增长问题。如果一直添加value数据,没有定期的删除机制、合理的过期机制或者卡量,大key只是早晚的问题。(例如:微博明星的粉丝列表、热门评论、直播弹幕等) 程序bug。某些异常情况导致某些key的生命周期超出预期,或者value数量异常增长。比如LIST的业务消费侧发生代码故障,造成对应Key的成员只增不减。 找到大 key bigkeys 参数 参考官方文档:https://redis.io/docs/latest/develop/connect/cli/#scanning-for-big-keys 使用redis-cli命令客户端,连接Redis服务的时候,加上 --bigkeys 参数,可以以遍历的方式分析Redis实例中的所有Key,并返回Key的整体统计信息与每个数据类型中Top1的大Key。(原理是基于SCAN) $ redis-cli --bigkeys # Scanning the entire keyspace to find biggest keys as well as # average sizes per key type. You can use -i 0.01 to sleep 0.01 sec # per SCAN command (not usually needed). [00.00%] Biggest string found so far 'key-419' with 3 bytes [05.14%] Biggest list found so far 'mylist' with 100004 items [35.77%] Biggest string found so far 'counter:__rand_int__' with 6 bytes [73.91%] Biggest hash found so far 'myobject' with 3 fields -------- summary ------- Sampled 506 keys in the keyspace! Total key length in bytes is 3452 (avg len 6.82) Biggest string found 'counter:__rand_int__' has 6 bytes Biggest list found 'mylist' has 100004 items Biggest hash found 'myobject' has 3 fields 504 strings with 1403 bytes (99.60% of keys, avg size 2.78) 1 lists with 100004 items (00.20% of keys, avg size 100004.00) 0 sets with 0 members (00.00% of keys, avg size 0.00) 1 hashs with 3 fields (00.20% of keys, avg size 3.00) 0 zsets with 0 members (00.00% of keys, avg size 0.00) 总结 优点:方便、快速、安全。 缺点:分析结果不可定制化,准确性与时效性差。 Redis RDB Tools工具 使用支持定制化分析的开源工具Redis RDB Tools,分析RDB文件,扫描出Redis大key。可以根据自己的精细化需求,全面地分析Redis实例中所有Key的内存占用情况,同时也支持灵活地分析查询。 ...

2025年3月15日 · 2 分钟

计算模型与主定理

例题 使用主定理 主定理是一个强大的工具,用于解决递归关系式,通常用于分析某些递归算法的时间复杂度。当递归关系式具有以下形式时: ...

2024年3月16日 · 2 分钟

Java 设计模式笔记

设计模式 举个例子,比如我们有个导出功能,按照不同的格式导出数据,比如 CSV、Excel、JSON 等;比如我们需要导入 execl 文件,需要解析文件内容,针对不同的格式,需要不同的解析方式;再比如我们有一个给用户发送邮件功能,有可能需要用 gmail, qq, 163 等邮箱服务商,手机验证码服务也是如此。如果我们使用 if-else 或者 switch-case 来实现,代码会变得很臃肿,而且扩展性很差,多一个导出为其他格式的需求,就需要大量修改代码。 处理之前的代码可能长这样: /** * @param filePath 文件路径(含文件名) * * 导出 CSV 和 Excel 的需求 */ public void export(String filePath) { String fileType = filePath.substring(filePath.lastIndexOf(".") + 1); if ("csv".equals(fileType)) { // 导出 CSV 的具体代码,以下省略100行 } else if ("excel".equals(fileType)) { // 导出 Excel 的具体代码,以下省略100行 } else { throw new IllegalArgumentException("不支持的文件类型:" + fileType); } } 策略模式 策略模式的主要作用是用来提升一些代码的复用性的,或者解决代码中出现很多 if-else 语句的问题。 ...

2024年1月21日 · 4 分钟

双系统场景下 Windows 引导修复记录

这篇是个人踩坑记录:双系统(Ubuntu + Windows)场景里,Windows 引导偶发丢失时如何恢复。 场景 双系统环境下,Windows 启动项异常消失 常见触发原因包括系统自修复、分区调整、引导项被覆盖等 修复准备 一块可启动的 Windows 安装 U 盘 从 U 盘启动,进入“疑难解答 -> 命令提示符” 修复步骤 diskpart list disk select disk 0 list part list vol select volume <EFI分区卷号> assign letter=K: exit bcdboot C:\Windows /s K: /f UEFI /l zh-cn 执行完成后重启,通常即可恢复 Windows 引导。 经验备注 关键点是定位正确的 EFI 分区并挂载盘符 bcdboot 是重建引导文件的核心命令 如果机器有多块硬盘,select disk / select volume 要结合实际确认,避免误操作 这类操作有系统级风险,建议先备份重要数据再处理。

2023年11月25日 · 1 分钟

背包问题算法笔记

好早以前写的,搬运一下 01一维背包 有 n 种物品要放到一个袋子里,袋子的总容量为 m,第 i 种物品的体积为 vi,把它放进袋子里会获得 wi 的收益,每种物品至多能用一次,问如何选择物品,使得在物品的总体积不超过 m 的情况下,获得最大的收益?请求出最大收益 #include<bits/stdc++.h> using namespace std; int main() { int dp[100010]{}; int n,m; cin>>m>>n; int v,w; while(n--){ cin>>v>>w; for(int i = m ; i >= v; i--) dp[i]=max(dp[i],dp[i-v]+w); } cout<<dp[m]; } 01背包二维 有 n(1 ≤ n ≤ 100) 种物品要放到一个袋子里,袋子的总容量为 m(1 ≤ m ≤ 100) ,我们一共有 k(1 ≤ k ≤ 100) 点体力值。第 i 种物品的体积为 vi(1 ≤ vi ≤ 100) ,把它放进袋子里会获得 wi(1 ≤ wi ≤ 100) 的收益,并且消耗我们 ti(1 ≤ ti ≤ 100) 点体力值,每种物品只能取一次,问如何选择物品,使得在物品的总体积不超过 m 并且花费总体力不超过 k 的情况下,获得最大的收益?请求出最大收益。 #include<bits/stdc++.h> using namespace std; int main() { int n, k, m; cin>>k>>m>>n; int dp[440][440]{}; int v, t, w; while(n--){ cin>>v>>t>>w; for(int i = k; i >= v; i--){ for(int j = m; j >= t; j--){ dp[i][j] = max(dp[i][j],dp[i-v][j-t]+w); } } } cout<<dp[k][m]; return 0; } 完全背包 有 n 种物品要放到一个袋子里,袋子的总容量为 m,第 i 种物品的体积为 vi,把它放进袋子里会获得 wi 的收益,每种物品能用无限多次,问如何选择物品,使得在物品的总体积不超过 m 的情况下,获得最大的收益?请求出最大收益。 #include<bits/stdc++.h> using namespace std; int main() { int dp[100010]{}; int m, n; cin>>m>>n; int v,w; while(n--){ cin>>v>>w; for(int i = v; i <= m; i++){ dp[i]=max(dp[i],dp[i-v]+w); } } cout<<dp[m]; } 多重背包 有 n 种物品要放到一个袋子里,袋子的总容量为 m,第 i 种物品的体积为 vi,把它放进袋子里会获得 wi 的收益,可以用 li 次,问如何选择物品,使得在物品的总体积不超过 m的情况下,获得最大的收益?请求出最大收益。数据规模为 1 ≤ n, m, l ≤ 1000 #include<bits/stdc++.h> using namespace std; int main() { int n, m; int dp[100010]{}; cin>>n>>m; int w, v, l; for(int i = 0 ; i < n ; i ++){ cin>>w>>v>>l; while(l--){ for(int j = m; j >= v; j--){ dp[j] = max(dp[j], dp[j-v] + w); } } } cout<<dp[m]; } 多重背包的单调队列优化 多重背包的题: 有 n 种物品要放到一个袋子里,袋子的总容量为 m,第 i 种物品的体积为 vi,把它放进袋子里会获得 wi 的收益,可以用 li 次,问如何选择物品,使得在物品的总体积不超过 m的情况下,获得最大的收益?请求出最大收益。数据规模为 1 ≤ n, m, l ≤ 1000 多重背包的二进制优化 : 题目同上,数据规模为 1 ≤ n, m, l ≤ 2000。用一个定理优化一下。定理:令 t 为最大的 i满足 2i+1 − 1 ≤ n,我们从 1, 2, 4, . . . , 2t, n − 2t+1 + 1 中选一些数字相加(可以不选),可以得出任意 [0, n] 内的值,每个数字只能用一次。每一种物品就只有 log l 个打包,然后对于每一组打包去跑 01 背包,可以把时间复杂度从 O(nm∑l) 降到 O(nm log∑l)。 但显然二进制优化不如单调队列优化,所以我们为什么不直接上单调队列优化呢? 二进制优化的数据范围:题目同上,数据规模为 1 ≤ n, m, l ≤ 10000。 //二进制优化,抄的谷粒多的板子 #include<bits/stdc++.h> using namespace std; int n, m, v, w, l; int dp[2005]; int main() { cin >> n >> m; while (n--) { cin >> v >> w >> l; for (int k = 1; k <= l; l -= k, k <<= 1) for (int i = m; i >= v * k; i--) dp[i] = max(dp[i], dp[i - v * k] + w * k); for (int i = m; i >= v * l; i--) dp[i] = max(dp[i], dp[i - v * l] + w * l); } cout << dp[m]; return 0; } //单调队列优化 //没看懂,还是抄的谷粒多板子。。。 #include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<queue> #include<string> using namespace std; int n, m, V[210], W[210], C[210]; int f[20010], q[20010]; int calc(int i, int u, int k) { return f[u + k * V[i]] − k * W[i]; } int main() { cin >> n >> m; memset(f, 0xcf, sizeof(f)); // −INF f[0] = 0; for (int i = 1; i <= n; i++) { scanf("%d%d%d", &V[i], &W[i], &C[i]); for (int u = 0; u < V[i]; u++) { int l = 1, r = 0; int maxp = (m - u) / V[i]; for (int k = maxp - 1; k >= max(maxp - C[i], 0); k--) { while (l <= r && calc(i, u, q[r]) <= calc(i, u, k)) r--; q[++r] = k; } for (int p = maxp; p >= 0; p--) { while (l <= r && q[l] > p - 1) l++; if (l <= r) f[u + p * V[i]] = max(f[u + p * V[i]], calc(i, u, q[l]) + p * W[i]); if (p - C[i] - 1 >= 0) { while (l <= r && calc(i, u, q[r]) <= calc(i, u, p - C[i] - 1)) r--; q[++r] = p - C[i] - 1; } } } } int ans = 0; for (int i = 1; i <= m; i++) ans = max(ans, f[i]); cout << ans << endl; } 分组背包 有 n 种物品要放到一个袋子里,袋子的总容量为 m。第 i 个物品属于第 ai 组,每组物品我们只能从中选择一个。第 i 种物品的体积为 vi,把它放进袋子里会获得 wi 的收益,可以用 li 次,问如何选择物品,使得在物品的总体积不超过 m 的情况下,获得最大的收益?请求出最大收益。数据规模为 1 ≤ n, m, l ≤ 1000 #include<bits/stdc++.h> using namespace std; int main() { int n, m, v, w, dp[1010]{}, a; cin>>n>>m; vector<pair<int,int>>c[1010]; for(int i = 1; i<= n; i++) { cin>>a>>v>>w; c[a].push_back({v,w}); } for(int i =0 ; i<=1000;i++){ for(int j = m; j>=0; j--){ for(auto [l,r]:c[i]){ if(v<=j) dp[j]=max(dp[j],dp[j-l]+r); } } } cout<<dp[m]; return 0; } 混合背包 混合背包比较愚蠢,很显然,如果是01,那么就直接取01;如果是完全,那就完全,如果是多重那就多重。 #include<bits/stdc++.h> using namespace std; int main() { int dp[100010]{}, n, m; cin>>n>>m; int t, v, w; while(n--){ cin>>t>>v>>w; if(t==1){ for(int i = m; i >= v; i--) dp[i] = max(dp[i], dp[i-v] + w); } else if(t==0){ for(int i = v; i <= m; i++) dp[i] = max(dp[i], dp[i-v] + w); } else { while(t--){ for(int i = m; i>= v; i--) dp[i] = max(dp[i], dp[i-v] + w); } } } cout<<dp[m]; return 0; } 有依赖的背包 题目:金明有 n 元钱,想要买 m 个物品,第 i 件物品的价格为: ,重要度为: 。有些物品是从属于某个主件物品的附件,要买这个物品,必须购买它的主件。 目标是让所有购买的物品的: 之和最大。 这种题目比较愚蠢,就懒得写代码了,把主件当01背包做,把附件的代码+主件的代价,再把附件当做分组背包做即可。 k优解 不会 前后缀分解 显然对于01背包而言,存在需要放进去的物品和不需要放进去的物品,现在清楚姐姐有个问题,题目如下: ...

2023年11月24日 · 6 分钟

ripgrep 高效文本搜索实战速查

ripgrep(命令是 rg)是一个命令行搜索工具,支持递归搜索和正则匹配。它速度快、体验好,并且会尊重 .gitignore,对代码仓库尤其友好。 安装 可以按你的操作系统安装,安装后用下面命令确认: rg --version 常用命令 1) 当前目录递归搜索 rg pattern 2) 按文件类型搜索 rg pattern -tjs 3) 忽略大小写 rg -i pattern 4) 统计匹配次数 rg -c pattern 5) 只输出命中文件名 rg -l pattern 6) 反向匹配(不包含 pattern) rg -v pattern 7) 按完整单词匹配 rg -w pattern 8) 指定搜索目录 rg pattern /path/to/search 9) 从文件读取多个搜索模式 rg -f patterns.txt 10) 与其他命令组合 统计命中文件数量: rg -l pattern | wc -l 进一步帮助 rg --help 如果你的主要工作是“在代码仓里快速定位信息”,rg 值得作为默认搜索工具。

2023年3月12日 · 1 分钟