Author Image

你好,我是 Paul

Paul

后端工程师 at Alibaba

一名后端工程师,关注 Java 生态、微服务与云原生技术,喜欢阅读源码和复盘线上问题。这个博客记录我的学习笔记与技术思考。

技能

工作经历

1

TODO

TODO —— 一句话介绍这家公司。

后端工程师(占位,待补充)

TODO - 至今

职责:
  • TODO —— 补充工作职责与产出

教育经历

计算机科学(占位,待补充)

项目

paul_blog
作者 2026 - 至今

基于 Hugo + Toha 主题的个人博客,从 Jekyll 迁移而来。

占位项目(待补充)
TODO TODO

TODO —— 在这里补充一个真实项目的介绍。

最新文章

Hero Image
记一次搜索迁移故障复盘

临近双十一,集团已经开始陆陆续续的封网,没有大的需求,都是些零零散散的需求在推进,整个人比较轻松。正好自己负责的系统有些历史架构问题可以在这个时候开始做起来了。 系统冗余了一份商货关系数据(商品和货品的绑定关系),当然这个份数据是个大宽表,除了绑定关系之外还冗余了商品 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变高等问题这些情况都是在小流量场景下没法复现的。 流量可监控。整个发布过程中我都是被动的等待问题出现,在迁移的过程中一定要有一些接口级别的异常监控,能帮我们主动发现问题。

    Hero Image
    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重点应用的地方我省略掉了,原因就是我不是专门从事这个语言的开发者,我更多的诉求只是利用它的便利性来当做一个小工具。如果真的想学习机器学习等方面的知识,本篇文章不提供任何参考价值。

      Hero Image
      Nacos-Config模块源码讲解

      前言 我们知道Nacos其实是由两个重要的模块组成,一是 Naming 模块,另一个就是今天要讲的 Config 模块。 版本说明 Nacos:2.1.1 jdk:1.8 代码分支:develope 配置中心基本原理 配置中心有三个角色分别是是配置发布者、配置服务端、配置客户端。整个流程由配置发布者发起,先发布一条配置到服务端,然后客户端监听这条配置,当配置有变动时由服务端推送到客用户端。 配置发布 我们从web层着手,可以很快的发现入口为com.alibaba.nacos.config.server.controller.ConfigController#publishConfig,这个方法看上去很长,但其实大体逻辑很简单,示意代码如下所示。 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,示意代码如下所示。

        成就

        占位成就(待补充)