首页 文章 精选 留言 我的

精选列表

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

记一次找因redis使用不当导致应用卡死bug的过程

首先说下问题现象:内网sandbox环境API持续1周出现应用卡死,所有api无响应现象 刚开始当测试抱怨环境响应慢的时候 ,我们重启一下应用,应用恢复正常,于是没做处理。但是后来问题出现频率越来越频繁,越来越多的同事开始抱怨,于是感觉代码可能有问题,开始排查。 首先发现开发的本地ide没有发现问题,应用卡死时候数据库,redis都正常,并且无特殊错误日志。开始怀疑是sandbox环境机器问题(测试环境本身就很脆!_!) 于是ssh上了服务器 执行以下命令 top 这时发现 机器还算正常,但是内心还是😖,于是打算看下jvm 堆栈信息 先看下问题应用比较耗资源的线程 执行 top -H -p 12798 找到前3个相对比较耗资源的线程 jstack 查看堆内存 jstack 12798 |grep 12799的16进制 31ff 没看出什么问题,上下10行也看看 于是执行 看到一些线程都是处于lock状态。但没有出现业务相关的代码,忽略了。这时候没有什么头绪。思考一番。决定放弃这次卡死状态的机器 为了保护事故现场 先 dump了问题进程所有堆内存,然后debug模式重启测试环境应用,打算问题再显时直接远程debug问题机器 第二天问题再现,于是通知运维nginx转发拿掉这台问题应用,自己远程debug tomcat。 自己随意找了一个接口,断点在接口入口地方,悲剧开始,什么也没有发生!API等待服务响应,没进断点。这时候有点懵逼,冷静了一会,在入口之前的aop地方下了个断点,再debug一次,这次进了断点,f8 N次后发现在执行redis命令的时候卡主了。继续跟,最后在到jedis的一个地方发现问题: /** * Returns a Jedis instance to be used as a Redis connection. The instance can be newly created or retrieved from a * pool. * * @return Jedis instance ready for wrapping into a {@link RedisConnection}. */ protected Jedis fetchJedisConnector() { try { if (usePool && pool != null) { return pool.getResource(); } Jedis jedis = new Jedis(getShardInfo()); // force initialization (see Jedis issue #82) jedis.connect(); return jedis; } catch (Exception ex) { throw new RedisConnectionFailureException("Cannot get Jedis connection", ex); } } 上面pool.getResource()后线程开始wait public T getResource() { try { return internalPool.borrowObject(); } catch (Exception e) { throw new JedisConnectionException("Could not get a resource from the pool", e); } } return internalPool.borrowObject(); 这个代码应该是一个租赁的代码 接着跟 public T borrowObject(long borrowMaxWaitMillis) throws Exception { this.assertOpen(); AbandonedConfig ac = this.abandonedConfig; if (ac != null && ac.getRemoveAbandonedOnBorrow() && this.getNumIdle() < 2 && this.getNumActive() > this.getMaxTotal() - 3) { this.removeAbandoned(ac); } PooledObject<T> p = null; boolean blockWhenExhausted = this.getBlockWhenExhausted(); long waitTime = 0L; while(p == null) { boolean create = false; if (blockWhenExhausted) { p = (PooledObject)this.idleObjects.pollFirst(); if (p == null) { create = true; p = this.create(); } if (p == null) { if (borrowMaxWaitMillis < 0L) { p = (PooledObject)this.idleObjects.takeFirst(); } else { waitTime = System.currentTimeMillis(); p = (PooledObject)this.idleObjects.pollFirst(borrowMaxWaitMillis, TimeUnit.MILLISECONDS); waitTime = System.currentTimeMillis() - waitTime; } } if (p == null) { throw new NoSuchElementException("Timeout waiting for idle object"); } 其中有段代码 if (p == null) { if (borrowMaxWaitMillis < 0L) { p = (PooledObject)this.idleObjects.takeFirst(); } else { waitTime = System.currentTimeMillis(); p = (PooledObject)this.idleObjects.pollFirst(borrowMaxWaitMillis, TimeUnit.MILLISECONDS); waitTime = System.currentTimeMillis() - waitTime; } } borrowMaxWaitMillis<0会一直执行,然后一直循环了开始怀疑这个值没有配置 找到redis pool配置,发现确实没有配置MaxWaitMillis,配置后else代码也是一个Exception 并不能解决问题 继续F8 public E takeFirst() throws InterruptedException { this.lock.lock(); Object var2; try { Object x; while((x = this.unlinkFirst()) == null) { this.notEmpty.await(); } var2 = x; } finally { this.lock.unlock(); } return var2; } 到这边 发现lock字眼,开始怀疑所有请求api都被阻塞了 于是再次ssh 服务器 安装arthas ,(Arthas是Alibaba开源的Java诊断工具) 执行thread命令 发现大量http-nio的线程waiting状态,http-nio-8083-exec-这个线程其实就是出来http请求的tomcat线程 随意找一个线程查看堆内存 thread -428 这是能确认就是api一直转圈的问题,就是这个redis获取连接的代码导致的, 解读这段内存代码 所有线程都在等@53e5504e这个对象释放锁。于是jstack 全局搜了一把53e5504e ,没有找到这个对象所在线程。 自此。问题原因能确定是 redis连接获取的问题。但是什么原因造成获取不到连接的还不能确定 再次执行 arthas 的thread -b (thread -b, 找出当前阻塞其他线程的线程) 没有结果。这边和想的不一样,应该是能找到一个阻塞线程的,于是看了下这个命令的文档,发现有下面的一句话 好吧,我们刚好是后者。。。。 再次整理下思路。这次修改redis pool 配置,将获取连接超时时间设置为2s,然后等问题再次复现时观察应用最后正常时干过什么。 添加一下配置 JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(); ....... JedisPoolConfig config = new JedisPoolConfig(); config.setMaxWaitMillis(2000); ....... jedisConnectionFactory.afterPropertiesSet(); 重启服务,等待。。。。 又过一天,再次复现 ssh 服务器,检查tomcat accesslog ,发现大量api 请求出现500, org.springframework.data.redis.RedisConnectionFailureException: Cannot get Jedis connection; nested exception is redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource fr om the pool at org.springframework.data.redis.connection.jedis.JedisConnectionFactory.fetchJedisConnector(JedisConnectionFactory.java:140) at org.springframework.data.redis.connection.jedis.JedisConnectionFactory.getConnection(JedisConnectionFactory.java:229) at org.springframework.data.redis.connection.jedis.JedisConnectionFactory.getConnection(JedisConnectionFactory.java:57) at org.springframework.data.redis.core.RedisConnectionUtils.doGetConnection(RedisConnectionUtils.java:128) at org.springframework.data.redis.core.RedisConnectionUtils.getConnection(RedisConnectionUtils.java:91) at org.springframework.data.redis.core.RedisConnectionUtils.getConnection(RedisConnectionUtils.java:78) at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:177) at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:152) at org.springframework.data.redis.core.AbstractOperations.execute(AbstractOperations.java:85) at org.springframework.data.redis.core.DefaultHashOperations.get(DefaultHashOperations.java:48) 找到源头第一次出现500地方, 发现以下代码 ....... Cursor c = stringRedisTemplate.getConnectionFactory().getConnection().scan(options); while (c.hasNext()) { .....,, } 分析这个代码,stringRedisTemplate.getConnectionFactory().getConnection()获取pool中的redisConnection后,并没有后续操作,也就是说此时redis 连接池中的链接被租赁后并没有释放或者退还到链接池中,虽然业务已处理完毕 redisConnection 已经空闲,但是pool中的redisConnection的状态还没有回到idle状态 正常应为 自此问题已经找到。 总结:springstringRedisTemplate 对redis常规操作做了一些封装,但还不支持像 Scan SetNx等命令,这时需要拿到jedis Connection进行一些特殊的Commands 使用stringRedisTemplate.getConnectionFactory().getConnection() 是不被推荐的 我们可以使用 stringRedisTemplate.execute(new RedisCallback<Cursor>() { @Override public Cursor doInRedis(RedisConnection connection) throws DataAccessException { return connection.scan(options); } }); 来执行, 或者使用完connection后 ,用 RedisConnectionUtils.releaseConnection(conn, factory); 来释放connection. 同时,redis中也不建议使用keys命令,redis pool的配置应该合理配上,否则出现问题无错误日志,无报错,定位相当困难。

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

苹果尴尬了: iOS 10.2.1 被爆致命 bug,怎么办?(附建议及测试视频)

iOS 10.2.1 自发布后,不少人使用该版本更新,但是推特上很多人表示,安装了iOS 10.2.1后出现了很多问题,整个人都不好了。 来看看,都有哪些问题: 1.多个用户反馈,升级为iOS 10.2.1 后,破坏了指纹识别器,TouchID 用不了了。这一问题首先在iPhone 6 和 iPhone 6 Plus 上出现,导致移除和添加指纹,恢复出厂设置都无效。但是,只要恢复系统到iOS10.2 ,就不会出现该问题,说明这一问题元凶正是iOS 10.2.1(现在有个噩耗:升级到iOS 10.2.1后,无法回到iOS10.2 了)。 【图片来源:福布斯官网】 2.升级后还会导致手机屏幕亮度在特定电池量时变暗。 在电池余量小于 75%时,屏幕亮度会调暗到原来亮度的 70%,在电池余量小于 49%时,屏幕亮度会调

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

帮量化团队搭了几年采集系统,90% 的"解析 bug"其实是代理层的锅

做量化数据这行几年,接手过不少半路瘫痪的采集系统。有个规律很稳定:团队来找我的时候,十有八九认为是解析代码出了问题,排查下来,真正的病灶几乎都在代理接入层——没有验证环节,没有容错设计,IP 一失效整条链路就断。因子逻辑写得再漂亮,数据不连续,一切白搭。 这篇把我平时给团队做落地时的完整流程写出来:按调度、请求、解析、存储四层搭,代理配置每一步都给可复制的代码和验证方法。照着做,第一轮采集是能跑通的。 ## **动手之前,先把三件事确认掉** 很多人跳过这一步直接写代码,后面每一步都会卡。三个前置条件缺一不可: 1. **数据源合规且公开。** 目标必须是公开页面或公开接口,不需要登录授权,采集行为遵守目标站点的 robots 协议。舆情、电商价格、招聘信息这类另类数据,业内确实已经广泛用于业绩预测和因子挖掘(21 财经有过相关报道),但前提永远是公开、合规。数据源需要账号授权或者属于非公开数据的,先去解决授权问题,别带着不合规的源进采集环节。 1. **Python 3.8+,pip 可用。** 没什么好说的,基础中的基础。 1. **代理服务已开通,鉴权配好。** 拿到接入地址或提取链接再开工。 ## **环境准备:每个工具装它的理由** 我一直反对"全家桶式"装依赖。最小可用环境就四类组件,每个对应一个明确职责: |组件|我的选择|理由| | --- | --- | --- | |HTTP 请求库|requests|接口简单,社区资料多,带代理请求足够用| |数据处理|pandas|解析结果直接整理成因子计算能用的表结构| |任务调度|APScheduler|固定周期触发采集,替代手动执行| |本地存储|SQLite|零配置落库,验证期够用,正式期平滑换 PostgreSQL| 代理服务的选型逻辑跟工具不一样。说句实在话:**验证期最该看的不是资源池规模,而是能不能先测后买、接入改造成本低不低。** 我现在给团队推荐的做法是找支持免费测试的服务商——比如极安代理对新注册用户开放 8 小时免费测试,API 快速接入,白名单和账密两种鉴权都支持。把下面的流程完整跑一遍,数据拿到手了再谈采购。采购决策放在验证之后,这个顺序别反。 ## **系统分层:四层结构,各管各的** 这套分层是我踩坑踩出来的通用划分,核心价值在于**解耦**:任何一层出问题,不污染其他层的逻辑,排查的时候能快速定位。 |层级|职责|出问题的典型表现| | --- | --- | --- | |调度层|控制采集频率与任务队列|数据断档、任务堆积| |请求层|代理接入、超时控制、失败重试|大面积请求失败| |解析层|从响应中提取目标字段|字段缺失、脏数据入库| |存储层|去重、增量写入|重复记录、历史无法回溯| 一个新手常犯的错:采集频率拍脑袋定。频率必须在调度层就跟数据源的更新节奏对齐,评估数据源要看频率、延迟、格式三要素。日更的电商价格数据按小时采,纯属浪费预算;分钟级更新的舆情数据按天采,信号早跑没了。 ## **第一步:配代理接入,先验证再往下走** requests 的代理配置就一个 `proxies` 字典,两种鉴权方式两种写法。 **白名单方式**:先在代理服务后台把本机公网 IP 加进授权列表,代码里只写接入地址。适合出口 IP 固定的服务器环境: ``` proxies = { "http": "http://host:port", "https": "http://host:port", } ``` **账密方式**:用户名密码直接写进接入地址。本地调试、出口 IP 不固定的环境用这个: ``` proxies = { "http": "http://user:pass@host:port", "https": "http://user:pass@host:port", } ``` 配完别急着写业务逻辑。**我的习惯是任何代理配置必须先过回显验证**,向 IP 回显接口发一次请求,对比返回的 IP: ``` import requests r = requests.get("https://httpbin.org/ip", proxies=proxies, timeout=8) print(r.json()) ``` 返回的 IP 和代理出口一致,链路才算生效。这个判定标准业内是共识,腾讯云开发者社区的 Requests 代理指南也是同样的做法。跳过这一步的人,后面排查问题时会分不清是代理没生效还是目标站封了你。 ## **第二步:封装请求模块,裸调 requests.get 不能进生产** 这是我 review 别人代码时说得最多的一句话。生产环境的请求模块必须内置三重容错:**超时、重试、换 IP**。我的参数经验值:超时 5-10 秒,重试 3 次,按 2 的幂次退避,每次重试前更换代理: ``` import time import requests def fetch(url, get_proxy, retries=3): for i in range(retries): try: r = requests.get(url, proxies=get_proxy(), timeout=8) r.raise_for_status() return r.text except requests.RequestException as e: print(f"第{i+1}次失败:{e}") time.sleep(2 ** i) return None ``` `get_proxy` 是代理提取函数:用短效类代理就在这里返回新提取的 IP,用隧道类代理返回固定接入地址即可。另外提醒一句,正式跑的时候把 `print` 换成 `logging` 落盘,捕获异常并记日志是排查代理问题的基础,没有日志的采集系统等于裸奔。 **验证方法**(我强烈建议做这个"破坏性测试"):故意填一个无效代理地址跑一遍,日志应该显示重试 3 次、间隔按 1 秒、2 秒、4 秒递增,最终返回 `None` 而不是程序崩溃。能优雅失败的模块才配上线。 ## **第三步:调度和增量存储,各十行代码跑通验证版** APScheduler 按固定间隔触发采集函数,间隔取值参照前面说的数据源更新节奏: ``` from apscheduler.schedulers.blocking import BlockingScheduler sched = BlockingScheduler() sched.add_job(collect_task, "interval", minutes=30) sched.start() ``` 存储层的去重方案:用数据源主键加时间戳做去重键,SQLite 建表时把去重键设为唯一索引,写入用 `INSERT OR IGNORE`,重复数据自动跳过。 **验证方法**:连续手动触发两轮采集,第二轮结束后查总行数。行数持平,去重生效;行数翻倍,回去检查唯一索引建没建成功。这个测试两分钟,能省掉后面清洗重复数据的两小时。 ## **报错排查表:按出现频率排序,先查前两条** 这张表是我这几年积累的排错顺序,按高发程度从上往下排,大部分问题查前两条就能解决: |报错信号|高发原因|修复动作| | --- | --- | --- | |ProxyError / ConnectionError|代理 IP 已过存活期,或 host:port 写错|核对提取时间与存活档位,重新提取 IP 再试| |407 Proxy Authentication Required|鉴权未通过|白名单确认公网 IP 已授权;账密核对 user:pass 写法| |ReadTimeout|目标响应慢或代理链路拥塞|timeout 上调至 10 秒,重试时换 IP| |429 Too Many Requests|触发目标站频率控制|降并发,请求间加 1-3 秒随机间隔| |响应乱码 / ContentDecodingError|压缩编码未正确处理|查响应头 Content-Encoding,手动解压再解析| 多说一句 ProxyError 为什么排第一:短效类代理的 IP 有固定存活期,到期自动失效,拿失效 IP 发请求只会得到连接错误。新手遇到这个错的第一反应往往是怀疑自己代码写错了,然后开始改代码。**正确的排查顺序是先看提取时间,再看存活档位,最后才轮到代码。** 记住这个顺序能少熬很多夜。 ## **进阶:并发、成本、稳定性,按需选用别硬套** 基础流程跑通之后再看这部分。三个方向我都标了适用场景,不匹配就不必上: **并发扩展**:适用于分钟级更新的舆情、行情类数据,单线程采不完的情况。高并发下逐个提取 IP 会变成新瓶颈,我的经验是把换 IP 动作交给云端:隧道代理统一入口接入,云端毫秒级自动换 IP、异常 IP 自动切换,程序端并发逻辑一行不用改。极安代理的隧道产品默认 5M 带宽、每秒 5 个请求的基础配置,对多数中等规模采集够用。 **成本控制**:适用于策略验证期、临时性采集任务。验证期最怕为用不满的资源付费,计费粒度要能对上任务周期。短效代理按每日 IP 数计费的模式对这类场景很友好,像极安 1000 IP 低至 3.6 元/天起,存活 1-15 分钟五档可选,几个小时的临时任务不用背整月套餐。 **可用率监控**:适用于 7×24 小时持续采集。在请求模块里统计每小时的请求成功率和平均耗时,成功率连续两个周期低于 95% 就触发告警。告警的成本永远远低于事后补数据的成本,这句话建议贴在工位上。 --- 以上就是全套流程。总结成一句话:**采集系统的稳定性是设计出来的,不是修出来的**。验证环节前置、容错三件套齐全、每层各管各的,系统才谈得上可维护。 评论区欢迎交流,尤其想听听大家在高并发场景下的换 IP 方案,以及 429 频控的其他实战处理经验。

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

AXUI 前端框架发布 v3.1.7 版本,优化了移动终端适配,处理了部分 BUG

2025-05-10更新 发布v3.1.7版本 核心js:https://unpkg.com/@codady/axui@3.1.7/dist/js/ax.js 核心css:https://unpkg.com/@codady/axui@3.1.7/dist/css/ax.css 打包文件:https://unpkg.com/@codady/axui@3.1.7/dist.zip 更新内容 对内置的none图片,去掉了“暂无图片”中文字样,在多语环境下更加通用。 修改Bubble气泡模块,将参数padding改为{enable,body,foot}。 修复checkboxes和radios模块属性content解析字符串为空的问题。 修复checkboxes的全选按钮无效的问题。 新增checkboxes全选按钮的形式,单个checkbox和radio也可全选。 修改checkbox中type=switch的样式,改小了尺寸。 修改checkbox组件,新增lang.on和lang.off属性,在type=switch时可能需要。 修复fieldTools.getVals方法从radio和checkbox取值失败的问题。 修复Valid模块对自定义表单组件的支持缺陷。 修改apart类型field表单域布局,当没有label和help节点时取消上下margin。 修改InputElem组件,增加feature=sign特色类型。 修改TextareaElem组件,增加feature=plain特色类型。 修正Datetime模块/组件宽度溢出的错误。 修改Dialog和Popup模块的最大高度,确保超多内容时可滚动显示。 修改Popup模块,在移动端将hover触发方式自动修正为click。 删除getClientType、ceils、isMobi、getFullGap、curveVals、prfix、alias、getImg*等函数。 修复ajax函数关于重复请求部分的错误逻辑。 修复模块之间循环引用的错误。 修复Popup模块,在手机端自适应时,left-max和right-max自动转成top-max和bottom-max。 缩小Datetime模块在手机端中日期单元格的尺寸,以确保在手机端至少可以完整显示两个面板。

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

Proxy-Go 控制面板 v4.7 发布,专业全能代理,修复 BUG 建议升级

Proxy-Go 控制面板 ProxyAdmin是强大的代理服务工具snail007/goproxy的控制面板,运行了它,一秒让你的服务器变为强大的代理服务器,友好的交互界面,小白也能轻松上手,让你用起来得心应手,心情舒畅。 更新内容: 1. 升级 sdk 到最新版 v13.9。 更新: v3.0 及以后版本,执行:proxy-admin update,即可完成快速更新到最新版。 功能预览 展现客户端参数 实时日志 参数文件管理 调试模式 下载地址: Gitee GitHub

资源下载

更多资源
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文件系统,支持十年生命周期更新。

优质分享App

优质分享App

近一个月的开发和优化,本站点的第一个app全新上线。该app采用极致压缩,本体才4.36MB。系统里面做了大量数据访问、缓存优化。方便用户在手机上查看文章。后续会推出HarmonyOS的适配版本。

Sublime Text

Sublime Text

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

用户登录
用户注册