Paul Paul Public Builder & 全栈工程师
search

你好,我是 Paul

以工程构筑现实,
用笔记沉淀思考。

Public Builder & 全栈工程师 · 关注 AI产品构建 / 全栈开发 / 服务稳定性

▍

19 技术文章
3 文章分类
22 覆盖标签
2022 持续写作始于
Paul
location_on中国 · 杭州 UTC+8
“半年前觉得优雅的实现,今天再看总有更好的解法。认知在演进,代码却停留在写下的那一刻。所以我忠实地记录着当时理解问题的方式,把每次在系统设计上的取舍、复盘的结论都写下来。—— 代码是当下认知的产物,笔记是与未来自己的持续对话”
terminalzsh, git, vim memoryPublic Builder & 全栈工程师

从后端深耕,到全栈与 AI 应用的持续演进

你好,我是 Paul,坐标杭州。6 年后端 + 2 年全栈:在东莞银行做过互联网直销放贷系统与网关架构升级,在阿里巴巴速卖通参与自营商品从零建设、主导货品系统向供应链中台迁移,也在 AI 创业公司担任过技术负责人。

在亿级数据、上万 QPS 的系统上主导数据迁移、查询优化与架构升级,对系统稳定性有切身理解;近两年转型全栈,日常重度使用 Coding Agent、Design Agent 等 AI 工具链。这个博客记录我在源码阅读、线上问题复盘与技术选型中的真实思考——不追求完美定稿,追求持续演进。

coffee

高并发系统设计

亿级数据、万级 QPS 系统的稳定性设计与实践、架构升级与工程性能优化。

deployed_code

全栈与出海业务

小程序与海外 APP 从零到一,打通跨境收单、结汇资金链路。

smart_toy

AI 工程实践

用 Coding Agent、Design Agent 等 AI 工具链重塑日常开发与交付方式。

System Prompt & Persona
Paul v2.0

角色设定:后端出身的全栈工程师,务实严谨。先理解业务再动手设计,用证据与数据说话;主导过亿级系统的数据迁移与架构升级,对稳定性有本能警觉;写作偏好源码佐证与复盘视角,优先以最小复杂度解决真实问题。

经历

  1. business_center

    全栈开发

    杭州旭耶科技 · 跨境 B2B 创业团队

    2024.12 — 至今

    跨境 B2B 交易平台从零到一的全栈建设,覆盖交易链路、卖家工作台与商家运营工具体系。

    • ▸打通下单、商家发货、平台履约的交易全链路
    • ▸重构卖家前端站点(商品、卖家两大模块)
    • ▸从零构建运营站点
    • ▸基于 OpenClaw + Playwright 搭建选品、铺货、广告投流、员工效率统计工具
    Java Python React Playwright Spring Cloud
  2. business_center

    技术负责人

    杭州极匠科技 · AI 创业团队

    2024.04 — 2024.11

    从零到一帮助团队搭建微信小程序、Web、APP,统筹 CR、故障复盘、进度跟踪等研发流程管理。

    • ▸打通海外收单、结汇全资金链路,设计 T+7 资金回流方案,iOS 端上线美国市场
    • ▸从零搭建小说推文业务链路,收敛收益计算模型,新渠道平台接入压缩至 2 天
    JavaScript Java 微信小程序 跨境支付
  3. business_center

    应用开发工程师

    阿里巴巴 · 速卖通 AliExpress

    2021.06 — 2024.03

    参与速卖通自营业务的供给、销售经营与供应链货品等系统的从零建设,主导速卖通货品系统的架构升级以及向供应链中台的迁移。

    • ▸主导全托管商货融合、半托管升级的方案设计,按租户维度抽象通用能力,兼任货品域 PM
    • ▸主导 2 亿+ SKU 商货关系单表的分库分表迁移与搜索灰度切流,沉淀通用消息重投组件
    • ▸设计竞品价格力离线数据回流链路:维度聚合 + 消息触发 + Redis 队列控频
    • ▸从零到一建设自营销售/供货商品运营管理端,沉淀行业侧通用 Excel 导出方案
    Java 微服务 分库分表 消息队列
  4. business_center

    开发工程师

    深圳长亮科技 · 东莞银行直销银行团队

    2019.05 — 2021.05

    负责直销银行门户网关与开放平台建设,制定工程分层规范并沉淀文档,降低系统迭代成本。

    • ▸作为 PM 主导老网关向 Spring Cloud 体系升级与全链路压测,完成基金申赎、车位贷、小微贷等业务迁移
    • ▸设计开放平台 SOAP 协议接入:协议/报文/加密/业务分层抽象,以 SPI 支持业务定制扩展
    Java Spring Cloud 网关 SPI

工程能力

code

编程语言

Java JavaScript Python Node.js Shell
database

后端与中间件

Fastify FastAPI Spring Cloud 数据库 缓存 消息队列 分库分表
deployed_code

全栈与 AI 工具

Web H5 Claude Code Cursor Stitch
psychology

工程与协作

稳定性治理 技术 PM CR 与复盘 知识管理

近期文章

summary 2023-10-29 · 约 1 分钟

记一次搜索迁移故障复盘

临近双十一,集团已经开始陆陆续续的封网,没有大的需求,都是些零零散散的需求在推进,整个人比较轻松。正好自己负责的系统有些历史架构问题可以在这个时候开始做起来了。 系统冗余了一份商货关系数据(商品和货品的绑定关系),当然这个份数据是个大宽表,除了绑定关系之外还冗余了商品 SKU 的一些信息供展示和搜索。 DB 的数据量级在1000W+(单表),但是最近业务数据增长的有点快,数据量级很快超过了2000W+,DB搜索的性能已经比较低了,很多走索引的查询也要100ms以上。长期来看肯定要迁移数据,切换到分表。但是业务数据还在不停的增长,因此我的方案是先切换搜索引擎,解决DB查询慢的问题,然后后面找机会再进行分表的切换。 背景 其实在阿里内部使用搜索有两种备选方案。一种是使用 OpenSearch。好处是,运维简单、开发起来也很方便。但是功能有些限制,比如:最大分页数不能超过 5000。另一种是自建 HA3(openSearch 本质上是在 HA3 的基础上做了一个通用的解决方案,做了很多限制),这个需要人工去运维 HA3 服务,多了些理解、运维的成本,但好处是自由度很高。由于我这个搜索并不需要很多的定制化搜索的能力,因此我用的是 OpenSearch 这套方案。 所以接下来的事情就简单多了,直接把 DB 的数据导入到 OpenSearch,然后创建好索引,那就已经完成了搜索引擎的搭建,接下来就是使用 SDK 了,具体细节就不讲了,应该只花了一个星期不到就完成了搜索的切换。(只需要做两件事一是把请求入参转换成 OpeanSearch 的语法,二是把搜索的结果转换成原来的数据对象) 当时没有测试资源,我自己测了一个两天,把原来接口的入参都一个个的试了一遍,确保字段都能进行有效的搜索之后,就上线了。当然,为了避免出现问题之后能即使止血,我加了个开关。发布的时候也灰度了5个小时,当然这些并不能避免问题,只是希望出现问题之后能即使止血,及时发现。 问题 按理说我加了止血方案,同时又灰度了5小时,应该没大问题。但是很不幸,我唯独评估漏了一个项,线上 OpenSearch 所需要的资源。这就导致我的 OpenSearch 搜索服务被限流了。(OpenSearch 有 LCU 的限制,如果 LCU 超了申请时候的配额会引发查询限流)。 虽然上游重试下就可以解决这个问题,但是因为临近双十一,大家都希望系统稳定点,所以直接跟老板反馈了。。 复盘 从表面上看这个是一个很小的问题,查询限流是很常见的事情。但是回顾自己整个迁移的方案,我发现自己漏了两项。一是没有梳理上游依赖这个接口的场景,因为这个接口之前直接查的 DB,之前不会限流迁移之后会限流。所以,如果上游根本没有重试的机制,那会导致上游业务处理失败。这次还好可以让上游手动重试,万一上游还没法手动重试,那只能去订正数据了。二是根本没有做压测和资源评估这件事,当然这跟我不熟悉 OpenSearch 有关,但是压测这事儿是很大的疏漏,因为我的自测只是保障了接口功能上的正常,却忘掉了真实场景下会有突发流量的问题。 总结 所以我总结总结了下,做迁移还是有些原则性的东西需要去做,否则方案就不完整。 上下游依赖梳理。需要弄清楚影响范围,这样可以在上线前做好相应的预案,上线时出了问题也能及时找到相应的人处理。 资源评估。系统依赖的中间件需要哪些资源,比如:CPU、磁盘这些东西,最好有个量化的指标,这样也便于去申请。 保证功能完整。迁移前后的功能一定是要保证一致的,这只最重要得一个大前提。 压测模拟。因为在小流量和大流量的场景下需要保障的东西完全不同,最常见的就是接口限流,RT变高等问题这些情况都是在小流量场景下没法复现的。 流量可监控。整个发布过程中我都是被动的等待问题出现,在迁移的过程中一定要有一些接口级别的异常监控,能帮我们主动发现问题。

阅读全文 arrow_forward
21study 2022-09-04 · 约 1 分钟

21天Python学习计划

前言 从毕业以来我把自己的主要精力都花在学习中间件的设计实现、分布式算法的原理及使用场景、架构设计理论(方法论)。我认为自己java水平并没有很高,如果自己工作中用到的语言都学不好就跑去学其他语言相关的东西,那就是”捡了芝麻,丢了西瓜“。当然,更多的是没有使用场景,在工作中我基本上不可能跨语言编程来完成我的工作。可能是我年少时太无知,随着我编程经验的增长,我开始否定自己之前的看法。我发现一些曾经的小语种也变得流行起来,而且在一些细分领域上他们似乎更加有优势。 举个例子来说,我发现在日常工作中我经常会和excel打交道,这个东西很烦,各种格式问题。我工作中最长见一个场景是运营给我excel让我帮忙刷数据。这种情况下使用java来完成说实话真的很烦,你需要去解析excel然后再去刷。我们知道excel烦就烦在我们需要对其格式进行调试,有时候一个单元格里面放的到底是字符串还是数字这个需要我们去调试。那这种情况下我们可以使用一些工具把excel里面的数据转化一下,让工具帮我们把数据处理成一个更容易解析的格式,那我们就可以直接得到结构化的数据,能省去很多debug的时间 那就直入主题,讲讲今天的主角Python。之所以选择针对Python进行一个21天的学习计划,主要目的是为了能学会Python的数据处理方式。 这套学习方法肯定不太会注重机器学习这方面,因为我不是做这方面的人才。这套学习计划主要针对Python基础语法、数据处理类库(Pandas、Numpy)的学习。如果有兴趣的朋友可以跟着学学看看,发现有些不合理的地方可以联系我改正。 第一周 学习内容 词法、句法、oop、基础数据类型的常见操作(尤其注意要熟练字符串的拼接、替换)。文件io(csv、txt、json、excel),原生os层面的io以及三方框架的io。 词法: 变量的定义、变量的种类、变量的作用范围 句法: 方法的定义、条件判断语句、选择语句、循环语句 oop: 类的定义、类的实例化、类的创建与销毁(只需要知道语句层面,不需要深入到虚拟机层面) 文件io: 这里只是学习API层面,如何使用好os、pandas库来读取文件。 相关资料 像计算机科学家那样思考 Python中文版第二版.pdf (这本书网上很多地方都有,自行下载就好) python读取文件的几种方式 Pandas库的学习 检验方式 字符串拼接、替换、查找判断。 利用循环打印杨辉三角。 利用递归解决斐波拉契数列问题。 文件读写,对数据进行排序、分组、去重等操作。 第二周 学习内容 数据分析工具的使用(numpy、pandas)、socket使用、http使用、正则的使用 检验方式 使用socket库搭建聊天服务器(需要用oop来抽象,并对服务端的socket通讯做到抽象隔离,让他能用任何方式来替换其实现。比如:目前基于系统socket库来实现,我们要保证换成其他库来实现仍然能保持最小改动量,只需要实现一个类就行。类似java的多态机制) http GET POST 等的请求的使用 爬取一个页面,获取页面上的所有图片、链接,并分别按照字符串倒序排序 使用数据分析工具分析日志 (统计错误次数、找到错误次数最多的错误信息) 相关资料 python 实现TCP socket通信和 HTTP服务器、服务器和客户端通信python实例 python实现http请求 轻松了解python正则表达式 (超详细,附举例) Python之numpy详细教程 如何在Pandas中实现类似于SQL查询的数据操作? Python pandas用法最全整理 第三周 学习内容 基于scitoll-learn来搭建机器学习模型 检验方式 跑通学习案例 相关资料 机器学习最佳Python库:Scikit-learn入门指南 关于机器学习中的Scikit-Learn,你不知道的10个实用功能 -在找到第一份数据科学工作前,我们可以通过哪些方法来获取工作经验? 写在最后 这里强调下,这篇文章主要侧重于把Python当做一个工具来使用。很多Python重点应用的地方我省略掉了,原因就是我不是专门从事这个语言的开发者,我更多的诉求只是利用它的便利性来当做一个小工具。如果真的想学习机器学习等方面的知识,本篇文章不提供任何参考价值。

阅读全文 arrow_forward
newest_tech 2022-08-22 · 约 7 分钟

Nacos-Config模块源码讲解

前言 我们知道Nacos其实是由两个重要的模块组成,一是 Naming 模块,另一个就是今天要讲的 Config 模块。 版本说明 Nacos:2.1.1 jdk:1.8 代码分支:develope 配置中心基本原理 配置中心有三个角色分别是是配置发布者、配置服务端、配置客户端。整个流程由配置发布者发起,先发布一条配置到服务端,然后客户端监听这条配置,当配置有变动时由服务端推送到客用户端。 配置发布 我们从web层着手,可以很快的发现入口为com.alibaba.nacos.config.server.controller.ConfigController#publishConfig,这个方法看上去很长,但其实大体逻辑很简单,示意代码如下所示。 java content_copy 复制代码 public class ConfigController { /** * Adds or updates non-aggregated data. * <p> * request and response will be used in aspect, see * {@link com.alibaba.nacos.config.server.aspect.CapacityManagementAspect} and * {@link com.alibaba.nacos.config.server.aspect.RequestLogAspect}. * </p> * @throws NacosException NacosException. */ @PostMapping @Secured(action = ActionTypes.WRITE, signType = SignType.CONFIG) public Boolean publishConfig(HttpServletRequest request, HttpServletResponse response, @RequestParam(value = "dataId") String dataId, @RequestParam(value = "group") String group, @RequestParam(value = "tenant", required = false, defaultValue = StringUtils.EMPTY) String tenant, @RequestParam(value = "content") String content, @RequestParam(value = "tag", required = false) String tag, @RequestParam(value = "appName", required = false) String appName, @RequestParam(value = "src_user", required = false) String srcUser, @RequestParam(value = "config_tags", required = false) String configTags, @RequestParam(value = "desc", required = false) String desc, @RequestParam(value = "use", required = false) String use, @RequestParam(value = "effect", required = false) String effect, @RequestParam(value = "type", required = false) String type, @RequestParam(value = "schema", required = false) String schema) throws NacosException { final String srcIp = RequestUtil.getRemoteIp(request); final String requestIpApp = RequestUtil.getAppName(request); if (StringUtils.isBlank(srcUser)) { srcUser = RequestUtil.getSrcUserName(request); } //check type if (!ConfigType.isValidType(type)) { type = ConfigType.getDefaultType().getType(); } // encrypted Pair<String, String> pair = EncryptionHandler.encryptHandler(dataId, content); content = pair.getSecond(); // check tenant ParamUtils.checkTenant(tenant); ParamUtils.checkParam(dataId, group, "datumId", content); ParamUtils.checkParam(tag); Map<String, Object> configAdvanceInfo = new HashMap<>(10); MapUtil.putIfValNoNull(configAdvanceInfo, "config_tags", configTags); MapUtil.putIfValNoNull(configAdvanceInfo, "desc", desc); MapUtil.putIfValNoNull(configAdvanceInfo, "use", use); MapUtil.putIfValNoNull(configAdvanceInfo, "effect", effect); MapUtil.putIfValNoNull(configAdvanceInfo, "type", type); MapUtil.putIfValNoNull(configAdvanceInfo, "schema", schema); ParamUtils.checkParam(configAdvanceInfo); if (AggrWhitelist.isAggrDataId(dataId)) { LOGGER.warn("[aggr-conflict] {} attempt to publish single data, {}, {}", RequestUtil.getRemoteIp(request), dataId, group); throw new NacosException(NacosException.NO_RIGHT, "dataId:" + dataId + " is aggr"); } final Timestamp time = TimeUtils.getCurrentTime(); String betaIps = request.getHeader("betaIps"); ConfigInfo configInfo = new ConfigInfo(dataId, group, tenant, appName, content); configInfo.setType(type); String encryptedDataKey = pair.getFirst(); configInfo.setEncryptedDataKey(encryptedDataKey); if (StringUtils.isBlank(betaIps)) { if (StringUtils.isBlank(tag)) { persistService.insertOrUpdate(srcIp, srcUser, configInfo, time, configAdvanceInfo, false); ConfigChangePublisher.notifyConfigChange( new ConfigDataChangeEvent(false, dataId, group, tenant, time.getTime())); } else { persistService.insertOrUpdateTag(configInfo, tag, srcIp, srcUser, time, false); ConfigChangePublisher.notifyConfigChange( new ConfigDataChangeEvent(false, dataId, group, tenant, tag, time.getTime())); } } else { // beta publish configInfo.setEncryptedDataKey(encryptedDataKey); persistService.insertOrUpdateBeta(configInfo, betaIps, srcIp, srcUser, time, false); ConfigChangePublisher.notifyConfigChange( new ConfigDataChangeEvent(true, dataId, group, tenant, time.getTime())); } ConfigTraceService.logPersistenceEvent(dataId, group, tenant, requestIpApp, time.getTime(), InetUtils.getSelfIP(), ConfigTraceService.PERSISTENCE_EVENT_PUB, content); return true; } } 整个方法就是先组装ConfigInfo,然后委托com.alibaba.nacos.config.server.service.repository.PersistService#insertOrUpdateTag进行处理,这里如果使用的是mysql那么实现类就是com.alibaba.nacos.config.server.service.repository.extrnal.ExternalStoragePersistServiceImpl,这个是一个插入数据库的动作就不细讲了,只是要注意的是这个方法包含新增和更新,当插入失败时会变成更新操作。而操作完数据库之后会发送一个ConfigDataChangeEvent事件,这个事件的处理类是com.alibaba.nacos.config.server.service.notify.AsyncNotifyService,示意代码如下所示。

阅读全文 arrow_forward

数字花园

持续生长、版本演进的非线性技术思考。这里不是完美的定稿文章,而是思想萌芽、实验实录与阅读笔记的温室。

进入花园 arrow_forward

保持联系。

无论是技术交流还是问题讨论,都欢迎写信给我。

paul@blog: ~

❯ whoami

backend & fullstack · java / javascript / node.js / python

❯ cat now.txt

写作与复盘进行中 · 最新文章见 /posts/

❯ uptime

since 2022-03-20 · 1669 days

❯