fanzhongwei

blog


  • 首页

  • 关于

  • 标签

  • 分类

  • 归档

  • 搜索

基于Dify + DeepSeek部署本地知识库

发表于 2025-05-10 | 更新于 2026-04-08 | 分类于 AI | 评论数:

¶基于Dify + DeepSeek部署本地知识库

本地部署的最大意义在于利用DeepSeek大模型的能力加上自己的知识库,可以训练出一个符合自己需求的大模型。

今天就来分享下这个搭建过程,使用基于LLM的大模型知识库问答系统Dify,里面集成DeepSeek以及私有知识库,打造一个符合自己需求的RAG应用。

¶1、安装Docker

安装Docker和Docker-compose,windows系统可以在docker网站 https://www.docker.com/ 下载docker desktop。

这里以linux为例,安装docker。

¶docker安装请参照官网教程

1
2
3
4
# docker
curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun
sudo service docker restart
sudo systemctl enable docker

¶Docker Compose

1
2
3
4
sudo curl -L "https://github.com/docker/compose/releases/download/v2.24.6/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose
docker-compose version

¶设置docker镜像源

1
2
3
4
5
6
vi /etc/docker/daemon.json
{"registry-mirrors":[
"https://registry.docker-cn.com",
"https://hub-mirror.c.163.com/"

]}

最后重启doker
systemctl restart docker

¶2、安装Dify

Dify文档参见:https://github.com/langgenius/dify

  1. clone dify源码:https://github.com/langgenius/dify.git
  2. 构建docker镜像,启动docker容器
1
2
3
4
cd dify
cd docker
cp .env.example .env
docker compose up -d
  1. 浏览器访问:http://localhost/install,进行初始化

¶3、配置模型

¶配置LLM模型

之前的文章我们已经介绍了,如何本地部署DeepSeek:https://note.youdao.com/s/mJWh4ya,接下来就以本地部署的DeepSeek为例添加到Dify中。

  1. 进入Dify页面,点击右上角头像-》设置,进入设置页面
  2. 选择模型供应商,找到Ollama
  3. 添加Ollama模型,如下图所示
    DeepSeek模型配置.png
    • 模型类型:LLM
    • 模型名称:下载的模型名称,例如:deepseek-r1:8b
    • 基础URL:Ollama部署的地址,默认端口为11434

¶配置Text Embedding模型

Text Embedding(文本嵌入)模型的核心作用是将文本(单词、句子、段落或文档)转换为稠密向量(即一组数值构成的向量),从而让计算机能够量化、理解和处理文本的语义信息。

Dify将使用Text Embedding模型将用户输入的问题转换为向量,到知识库根据语义快速检索知识。

Text Embedding模型如何选择,以下是主流Text Embedding模型的对比表格,从多个维度对比其优劣,帮助根据场景选择合适模型:

模型名称 参数量 上下文长度 多语言支持 特色优势 主要缺点 适用场景
OpenAI text-embedding-3 未知 8192 是 高准确度,OpenAI生态兼容 收费API,隐私数据需谨慎 商业应用,预算充足的项目
BAAI/bge-large 1.1B 512 是(侧重中英) 中文任务领先,开源可商用 长文本需分段处理 中文搜索、问答系统
Alibaba-NLP/gte-large 0.6B 512 是 阿里巴巴优化,文档理解强 资源消耗较大 电商、长文档处理
Google/Gecko 0.3B 1024 是 轻量高效,谷歌搜索优化 精度略低于大模型 移动端、实时检索系统
sentence-transformers/all-MiniLM 22M 256 是 超轻量级,推理速度快 表达能力有限 边缘设备、低延迟场景
Cohere/embed-multilingual 0.3B 512 是(100+语言) 多语言均衡表现 英文略逊于专用模型 跨国多语言应用
MokaAI/m3e-base 0.3B 512 中文优化 中文CL任务专项优化 非中文任务较弱 中文语义相似度计算

注:最新模型建议查看HuggingFace的MTEB排行榜(https://huggingface.co/spaces/mteb/leaderboard )获取实时评测数据。实际选择时应通过自己的测试集验证。

Dify配置Text Embedding模型步骤如下:

  1. 进入Dify页面,点击右上角头像-》设置,进入设置页面
  2. 选择模型供应商,找到Ollama
  3. 添加Ollama模型,添加:bge-large
    • 模型类型:Text Embedding
    • 模型名称:bge-large
    • 基础URL:Ollama部署的地址,默认端口为11434

¶配置Rerank模型

Rerank模型的主要任务是对Text Embedding模型初步筛选出的候选集通过深度语义理解进行重新排序,确保最相关的结果排在最前面。

Ollama中目前没有找到合适的Rerank模型,这里考虑从HugginFace中下载BAAI/bge-reranker-large模型,使用transformers搭建一个满足OpenAI-API-compatible协议类型的Rerank模型,方便加入到Dify中。

安装BAAI/bge-reranker-large模型请参考:如何从HuggingFace下载并搭建Rerank模型

Dify配Rerank模型步骤如下:

  1. 进入Dify页面,点击右上角头像-》设置,进入设置页面
  2. 选择模型供应商,找到OpenAI-API-compatible
  3. 添加OpenAI-API-compatible模型,添加:bge-reranker-large
    • 模型类型:Rerank
    • 模型名称:bge-reranker-large
    • 基础URL:http://ip:port/v1

¶4、构建知识库

到这里基础的模型已经配置完成,接下来我们开始搭建知识库,进入Dify的知识库页面点击创建知识库,选择文件后进行如下配置:

Dify创建知识库

然后点击保存并处理,等待索引处理完成,即可使用该知识库。

其中选择的模型在知识库检索过程中有不同的作用:

  • Text Embedding模型:
    • 用于召回阶段(Retrieval),从海量文档中快速筛选出Top-K(如1000条)候选结果。
    • 通过向量相似度(如余弦相似度)粗筛,保证高召回率(Recall)。
  • Rerank模型:
    • 用于排序阶段(Reranking),对Top-K候选结果精细排序。
    • 通过深度语义理解(如交叉注意力)计算查询-文档对的相关性,提升准确率(Precision)。

示例流程:用户查询 → [Embedding模型] → 召回1000条候选 → [Rerank模型] → 返回Top-10最相关结果

¶5、创建聊天应用

进入Dify的工作室,创建空白应用 -》选择Chatflow类型创建应用,进行工作流编排:

Dify创建应用.png

tips:知识库中的知识图片最好是具有语义的markdown格式(例如:![用户登陆.png](https://xxxx)),这样大模型才能更好的以图文方式回答用户的问题。

¶应用调试和发布

点击预览按钮可以对编排的工作流进行调试,其中每一步可点开查看具体输入和输出:
Dify应用预览.png

应用调试完成后,点击发布按钮即可进行发布,发布后点击运行可以打开Dify提供的默认聊天页面。Dify提供的聊天页面支持嵌入到其它网站,同时也提供一系列API接口以供开发者深度集成,大家可根据自己需求自由选择。

参考文档:

  • https://github.com/langgenius/dify
  • https://huggingface.co/spaces/mteb/leaderboard

如何从HuggingFace下载并搭建Rerank模型

发表于 2025-05-03 | 更新于 2026-04-08 | 分类于 AI | 评论数:

¶如何从HuggingFace下载并搭建Rerank模型

¶什么是Rerank模型

Rerank模型的主要任务是对Embedding模型初步筛选出的候选集进行重新排序,确保最相关的结果排在最前面。它通常基于更复杂的语义分析,评估候选文档和查询之间的深层次匹配关系。在Rerank阶段,模型会分析查询与候选文档之间的上下文、语义关系等信息。它可以使用诸如BERT/GPT等预训练语言模型来捕捉更细腻的语义和句子间的关系,从而对初步候选文档进行更精确的评分与排序。Rerank模型一般比Embedding模型计算更复杂,通常需要更多的计算资源,因此适合处理Embedding模型初步检索后的数据。

  • 初步检索(Embedding模型):用户输入查询后,Embedding模型首先将查询和文档表示为向量,然后通过向量相似度计算,快速从大规模数据集中筛选出若干个候选文档或候选答案。
  • 重新排序(Rerank模型):在得到初步候选集后,Rerank模型进一步分析这些候选文档或答案与查询之间的精确匹配程度,并根据复杂的语义关系重新打分,对候选集进行排序。

¶使用Docker安装

采用Docker部署模型,Docker调用GPU,需先安装nvidia-container-toolkits

从存储库更新包列表与安装NVIDIA Container Toolkit

1
2
sudo apt-get update
sudo apt-get install -y nvidia-container-toolkit

一键部署BAAI/bge-reranker-large模型。

1
docker run --gpus all -p 18080:80 -v /reranker:/data --pull always ghcr.io/huggingface/text-embeddings-inference:turing-1.5 --model-id BAAI/bge-reranker-large

¶直接安装

docker镜像源下载比较慢,这里采用python本地安装

  1. 确保系统已安装 Python 3.8 或更高版本,并安装 pip 包管理工具。
1
2
3
4
5
# 检查 Python 版本
python3 --version

# 更新 pip
python3 -m pip install --upgrade pip
  1. 安装依赖库
    BAAI/bge-reranker-large 是一个基于 Transformer 的模型,通常使用 transformers 库加载和运行。安装以下依赖:
1
pip install torch transformers
  • torch: PyTorch 是运行模型的基础框架。
  • transformers: Hugging Face 提供的库,用于加载和运行预训练模型。
  1. 下载模型

参考文档:https://huggingface.co/docs/transformers/v4.48.2/zh/installation#离线模式

1
2
3
4
5
6
7
from transformers import AutoTokenizer, AutoModelForSequenceClassification

tokenizer = AutoTokenizer.from_pretrained("BAAI/bge-reranker-large")
model = AutoModelForSequenceClassification.from_pretrained("BAAI/bge-reranker-large")

tokenizer.save_pretrained("/home/develop/ai-llm/bge-reranker-large/bge-reranker-large")
model.save_pretrained("/home/develop/ai-llm/bge-reranker-large/bge-reranker-large")
  1. 提供API服务

如果你不想微调模型,你可以直接安装包,不用finetune依赖:

1
pip install -U FlagEmbedding

如果你想微调模型,你可以用finetune依赖安装:

1
pip install -U FlagEmbedding[finetune]

使用 FastAPI 部署为服务:pip install fastapi uvicorn pydantic

vi bge-reranker-large.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from fastapi import FastAPI
from transformers import AutoModelForSequenceClassification, AutoTokenizer
import torch

app = FastAPI()

# 加载下载好的离线模型和分词器
model_name = "/your/path/bge-reranker-large"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForSequenceClassification.from_pretrained(model_name)
# 如果有GPU
model = model.to("cuda")

# 将模型设置为评估模式
model.eval()

@app.post("/rerank")
def rerank(query: str, document: str):
inputs = tokenizer(query, document, return_tensors="pt", truncation=True, padding=True)
with torch.no_grad():
# 如果有GPU
inputs = {k: v.to("cuda") for k, v in inputs.items()}
outputs = model(**inputs)
score = outputs.logits.item()
return {"score": score}

启动服务:uvicorn bge-reranker-large:app --reload --host 0.0.0.0 --port 5000

参考文档:https://github.com/FlagOpen/FlagEmbedding/tree/master/examples/inference/reranker#using-huggingface-transformers

访问API进行测试:

1
curl -X POST "http://127.0.0.1:5000/rerank" -H "Content-Type: application/json" -d '{"query": "What is the capital of France?", "document": "Paris is the capital of France."}'

产考文档:

  • https://huggingface.co/docs/transformers/v4.48.2/zh/installation#离线模式
  • https://github.com/FlagOpen/FlagEmbedding/tree/master/examples/inference/reranker#using-huggingface-transformers

DeepSeek本地部署

发表于 2025-04-30 | 更新于 2026-04-08 | 分类于 AI | 评论数:

¶DeepSeek本地部署

作为一款现象级的Ai产品,DeepSeek用户量暴增,服务器又被攻击,使用DeepSeek,经常出现服务器繁忙。

将DeepSeek部署在本地电脑就方便很多,选择对应的模型来下载,1.5b、7b、8b、14b、32b、70b或671b,这里有很多版本可选,模型越大,要求电脑内存、显卡等的配置越高。DeepSeek部署在本地电脑上部署,有些不方便公开的数据,比如实验数据、企业内部数据,可以被本地的大模型安全地使用了。

¶下载安装 Ollama

访问Ollama官网:https://ollama.com/download

选择对应操作系统进行安装,这里以linux为例:

1
curl -fsSL https://ollama.com/install.sh | sh

如果下载较慢,可以考虑使用docker方式安装:https://hub.docker.com/r/ollama/ollama

  • Install the NVIDIA Container Toolkit packages
1
sudo apt-get install -y nvidia-container-toolkit
  • Configure Docker to use Nvidia driver
1
2
sudo nvidia-ctk runtime configure --runtime=docker
sudo systemctl restart docker

启动容器

1
docker run -d --gpus=all --restart always -v ollama:/root/.ollama -p 11434:11434 --name ollama ollama/ollama

¶安装DeepSeek大模型

1
docker exec -it ollama ollama run deepseek-r1:8b

模型大小如何选择:

模型大小 参数量 显存需求 (GPU) 内存需求 适用场景
1.5B 15亿 2~4 GB 8 GB 低端设备,轻量推理
7B 70亿 8~12 GB 16 GB 中端设备,通用推理
8B 80亿 10~16 GB 16 ~ 32 GB 中高端设备,高性能推理
14B 140亿 16~24 GB 32 GB 高端设备,高性能推理
32B 320亿 32~48 GB 64 GB 高端设备,专业推理
70B 700亿 64 GB+ 128 GB 顶级设备,大规模推理
761B 6710亿 多GPU(80 GB+) 256 GB+ 超大规模推理,分布式计算

然后就可以开启对话了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
docker exec -it ollama ollama run deepseek-r1:8b
pulling manifest
pulling 6340dc3229b0... 100% ▕█████████████████████ 4.9 GB
pulling 369ca498f347... 100% ▕█████████████████████ 387 B
pulling 6e4c38e1172f... 100% ▕█████████████████████ 1.1 KB
pulling f4d24e9138dd... 100% ▕█████████████████████ 148 B
pulling 0cb05c6e4e02... 100% ▕█████████████████████ 487 B
verifying sha256 digest
writing manifest
success
>>> 请详细介绍你的来历
<think>
您好!我是由中国的深度求索(DeepSeek)公司开发的智能助手DeepSeek-R1。如您有任何任何问题,我会尽我所能为您提供帮助。
</think>

您好!我是由中国的深度求索(DeepSeek)公司开发的智能助手DeepSeek-R1。如您有任何任何问题,我会尽我所能为您提供帮助。

Use Ctrl + d or /bye to exit.

后续将介绍如何基于本地模型搭建自己的知识库。

时代洪流中的新工业革命:AI大模型如何重塑人类未来

发表于 2025-02-25 | 更新于 2026-04-08 | 分类于 AI | 评论数:

¶时代洪流中的新工业革命:AI大模型如何重塑人类未来


¶引言:当智能成为新蒸汽机

18世纪的蒸汽机解放了人类双手,电力革命点亮了现代文明,而今天,我们正站在以AI大模型为核心的第三次工业革命浪潮之巅。从GPT-4的创造性文本生成到DeepSeek以1/10参数击败GPT-4o的数学推理能力,从浙江水科“Shoot-AI”模型在文旅场景的爆发到Meta智能眼镜的实时翻译功能,这场革命不再局限于机械与能源,而是以数据和算法为燃料,重构人类社会的每一个细胞。


¶一、AI大模型:新工业革命的三大底层逻辑

1. 生产力跃迁:从“人力替代”到“智能涌现”
工业革命用机器取代体力劳动,而AI大模型正在用“认知自动化”取代脑力劳动。

  • 效率革命:杭州全诊医学通过ERNIE模型将病历生成效率提升20%,宁波传媒借助AI批改工具优化作文评审流程。
  • 成本颠覆:DeepSeek通过强化学习与模型蒸馏技术,将训练成本降低42.5%,推理效率提升40%,让小企业也能负担高性能AI应用。
  • 知识民主化:开源模型如LLaMA2和DeepSeek-R1打破巨头垄断,开发者仅需1块H100芯片即可训练场景模型,技术门槛降至历史最低。

2. 创新范式:从“线性迭代”到“涌现式突破”
当模型参数量突破万亿级,AI开始展现人类未曾预设的能力:

  • 跨模态创造:DALL·E 3生成的艺术品登陆苏富比拍卖,MidJourney将文字转化为电影级分镜,创作权从专业画家延伸至每个普通人。
  • 科学发现:AlphaFold破解2亿种蛋白质结构,DeepMind的GNoME模型发现220万种新材料,科研周期从十年缩短至数天。
  • 自主进化:DeepSeek通过纯强化学习实现“无监督成长”,仅凭答案正确性即可自我优化,逼近人类元认知能力。

3. 经济重构:从“资源占有”到“智能密度”
普华永道预测,到2030年AI将贡献15.7万亿美元全球经济增量,而竞争焦点已转向三大维度:

  • 算力主权:美国通过《芯片法案》投入520亿美元构建AI基础设施,中国“东数西算”工程打造10大数据中心集群,算力成为国家战略资源。
  • 场景霸权:浙江水科以“Shoot-AI”模型切入文旅赛道,为500家景区定制个性化游览视频,单月用户突破600万,证明垂直场景的落地价值远超通用模型。
  • 生态博弈:OpenAI与DeepSeek分别代表闭源与开源路线,后者通过开放API和极简定价(仅为GPT-4的1%),推动AI从“金字塔垄断”走向“分布式创新”。

¶二、拒绝AI=放弃生存权:企业与个人的终极挑战

1. 企业的生死抉择:从“+AI”到“AI原生”

  • 传统模式终结:依赖人工标注和云端调用的企业(如早期SaaS平台)正被淘汰,2025年“百模大战”后,存活者需具备三大能力:
    • 数据炼金术:美的集团通过AI中台将研发周期缩短50%,故障预测准确率达95%。
    • 端侧智能:Meta Ray-Ban眼镜集成本地化模型,实时翻译延迟低于0.5秒,用户隐私与体验兼得。
    • 伦理合规:欧盟《AI法案》要求算法透明性,百度的iRAG技术通过外部知识检索减少“幻觉”,成为合规标杆。

2. 个体的进化焦虑:从“技能持有”到“人机共生”
牛津大学研究显示,47%的职业将被AI深度改造,但危机中蕴藏机遇:

  • 创造力升级:设计师用Stable Diffusion生成100版初稿,再聚焦细节优化,效率提升10倍。
  • 认知升维:Prompt Engineering(提示词工程)成为必修课,顶尖工程师通过精准指令调动AI完成代码生成、数据分析等高阶任务。
  • 跨界突破:短视频创作者借助“简单AI”3步生成爆款文案,传统作家转型互动叙事设计师,AI工具让副业收入超过主业成为可能。

¶三、决胜未来的四大行动纲领

1. 技术范式:拥抱“小模型+强化学习”革命

  • 低成本落地:DeepSeek-R1以7B参数实现32B模型的性能,证明“模型蒸馏+强化学习”可大幅降低算力依赖。
  • 端侧爆发:三星50克AR眼镜、字节“豆包”助手预示边缘计算崛起,2025年端侧AI市场规模将突破5000亿美元。

2. 组织变革:构建“人机双螺旋”架构

  • 敏捷迭代:Salesforce的AI突击队2周推出CRM Copilot,传统企业需将开发周期从“年”压缩至“周”。
  • 数据驱动:丰田每天处理PB级驾驶数据训练自动驾驶模型,数据治理能力决定AI上限。

3. 生态卡位:抢占垂直场景制高点

  • 文旅革命:浙江水科为景区定制AI视频,游客停留时长增加40%,二次消费提升25%。
  • 医疗普惠:联影智能的AI育种系统提升作物产量20%,AI诊断工具在基层医院覆盖率突破60%。

4. 伦理觉醒:在创新与约束间平衡

  • 透明性:微软AETHER小组对所有AI产品进行安全审查,避免算法偏见。
  • 普惠性:OpenAI向免费用户开放o3-mini模型,DeepSeek开源代码推动技术民主化。

¶结语:重写文明的操作系统

1785年,卢德分子砸毁机器,却无法阻挡纺织女工被蒸汽机取代;2025年,抗拒AI者或将重演历史。但这一次,变革不再局限于某个行业——AI大模型正在重写整个人类文明的操作系统:

  • 生产维度:从“制造”到“智造”,个性化定制成本逼近大规模生产。
  • 认知维度:人类首次拥有超越生物脑容量的“外接智能”,知识获取效率指数级跃迁。
  • 文明维度:AI终端的普及让技术融入生活肌理,人类从“使用工具”进阶为“与智能体共生”。

正如李彦宏所言:“未来不属于大模型,而属于百万AI原生应用。”在这场洪流中,唯一的选择是跳入驾驶舱,以代码为杠杆,撬动属于智能时代的无限可能。

性能优化-高效生成全局唯一自增序列

发表于 2024-09-18 | 更新于 2026-04-08 | 分类于 性能优化 | 评论数:

¶性能优化-高效生成全局唯一自增序列

如何高效地生成全局唯一自增序列,一个设置让性能提升100倍。

¶背景

系统上线前性能测试时,对生成具有业务含义的全局唯一且自增的序列进行压测;200并发,4个8C16G的应用节点,压测结果如下:

类名名 方法名 调用次数 avg min max 90% pct 95% pct 99% pct
LockUtils forUpdateLockAndRun 5505 3821 11 35020 9110 11014 18045
LockExecutor executeLock 5505 36 6 389 88 122 208

这结果可以说是惨不忍睹~~~

如何获取方法级别的性能测试报告,详情请查看:性能优化利器-JavaAgent

¶生成全局唯一自增序列

生成具有业务含义的全局唯一且自增的序列,是采用SELECT FOR UPDATE方案实现,生成逻辑:

  • 1、select for update获取数据库序列配置的行锁
  • 2、对序列配置进行自增操作
  • 3、更新序列配置
  • 如果任何一步失败则进行重试,最多重试三次

核心源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
private static TransactionDefinition TRANSACTION_DEFINITION = new DefaultTransactionDefinition();

private static void forUpdateLockAndRun(Class<IEntity> lockEntityClass, QueryWrapper<IEntity> selectForUpdate, LockExecutor lockExecutor, int retryTimes) {
if (retryTimes >= 3) {
throw new ResourceUpdateFailedException("tryForUpdateLockAndRun失败,重试次数已达到上限,请稍后再试");
}
BaseMapper<IEntity> mapper = DB_OPERATE_SERVICE.getMapperByEntityClass(lockEntityClass);
TransactionStatus transactionStatus = TRANSACTION_MANAGER.getTransaction(TRANSACTION_DEFINITION);
IEntity lockData = null;
try {
selectForUpdate.last(" for update");
lockData = mapper.selectOne(selectForUpdate);
} catch (Exception e) {
log.error("tryForUpdateLockAndRun异常,获取锁失败,进行重试", e);
TRANSACTION_MANAGER.rollback(transactionStatus);
forUpdateLockAndRun(lockEntityClass, selectForUpdate, lockExecutor, retryTimes + 1);
return;
}
Assert.notNull(lockData, "tryForUpdateLockAndRun异常,未获取到需要锁定的数据,无法进行重试");
try {
// 对lockData进行自增操作
IEntity newLockData = lockExecutor.executeLock(lockData);
if (null != newLockData) {
selectForUpdate.last("");
mapper.update(newLockData, selectForUpdate);
}
TRANSACTION_MANAGER.commit(transactionStatus);
} catch (Exception e) {
log.error("tryForUpdateLockAndRun获取到锁,但更新数据失败,进行重试", e);
TRANSACTION_MANAGER.rollback(transactionStatus);
forUpdateLockAndRun(lockEntityClass, selectForUpdate, lockExecutor, retryTimes + 1);
}
}

public static interface LockExecutor {
IEntity executeLock(IEntity lockData);
}

¶什么是SELECT FOR UPDATE

SELECT FOR UPDATE是由数据库(MySQL、PostgreSQL 和 Oracle等)提供的一种事务锁定机制,用于在事务中锁定所选的行,以防止其他事务对这些行进行修改。

当一个事务执行SELECT FOR UPDATE语句时,数据库会对查询结果集中的每一行进行加锁。这些锁会一直保持到事务提交或回滚时才会释放。在此期间,其他事务无法对这些被锁定的行进行修改或删除操作,从而确保了数据的一致性。

SELECT FOR UPDATE的实际应用有:

  • 生成自增全局唯一标识:生成具有业务含义的全局唯一且自增的序列,避免生成重复序列。
  • 库存管理:确保在扣减库存时,只有一个事务能够成功更新库存数量,避免超卖问题。
  • 账户余额更新:在金融系统中,账户余额的更新需要确保数据的一致性。
  • 订单处理:锁定订单状态,确保订单状态的更新按照预期的顺序执行,避免并发问题

¶性能问题分析过程

通过方法的性能测试报告中可以发现forUpdateLockAndRun方法执行时间特别长,但executeLock方法的执行时间又特别短,同时查看应用的jbdc日志发现select for update的sql执行时间居然达到35秒:

1
2
3
select * from t_wybs_scpz where key='xxx_wybs' for update ##^^## select * from t_wybs_scpz where key=? for update
{executed in 35004 msec}
{resultSet rows 1, build in 0 msec}

¶怀疑缺少索引

查看t_wybs_scpz表结构发现有索引,分析sql的执行计划也确实走了索引:

1
2
3
4
5
6
7
8
9
10
explain 
select * from t_wybs_scpz where key='xxx_wybs' for update

=================================================================================================
|ID|OPERATOR |NAME |EST.ROWS|EST.TIME(us)|
-------------------------------------------------------------------------------------------------
|0 |MATERIAL | |1 |32 |
|1 |└─DISTRIBUTED FOR UPDATE | |1 |32 |
|2 | └─DISTRIBUTED TABLE RANGE SCAN|T_WYBS_SCPZ(I_WYBS_SCPZ_KEY) |1 |32 |
=================================================================================================

因此,可以排除缺少索引的嫌疑。

¶怀疑FOR UPDATE锁表

虽然SELECT FOR UPDATE是只索引查询返回的行,但是在某些情况下还是会锁表:

¶没有合适的索引或索引未使用

如果查询条件没有使用索引,或者查询的列没有合适的索引,数据库将进行全表扫描。数据库进行全表扫描时,需要先将数据加载到内存然后进行匹配(如果某行数据被锁住,这一步就会阻塞),因此即使只锁住一行数据也会表现出锁表的现象。

通过上面的sql执行计划可以确定索引已生效,排除这种可能。

¶锁升级

在某些数据库(如 Oracle)中,锁机制是行级别的。但如果事务中的行锁数量过多,数据库可能会触发锁升级,将行锁升级为表锁。锁升级的发生是为了减少系统开销,但可能会导致表级别的锁定。

通过jdbc日志{resultSet rows 1, build in 0 msec}发现,只返回了一行数据,排除这种可能。

综上所述,基本可以排除锁表的嫌疑。

¶怀疑事务长时间未结束

forUpdateLockAndRun方法中lockExecutor.executeLock(lockData)执行时间非常短(平均只有36ms),也就是说从获取到锁之后到事务结束平均耗时只有36ms。

那么是不是就可以排除事务长时间未结束的嫌疑呢,我们可以本地代码调试验证下:

  1. 在IEntity newLockData = lockExecutor.executeLock(lockData);添加断点,然后在数据库查询select * from t_wybs_scpz where key='xxx_wybs' for update,发现被锁住不能返回结果,符合预期。
  2. 在forUpdateLockAndRun方法执行结束后,然后在数据库查询select * from t_wybs_scpz where key='xxx_wybs' for update,发现被锁住不能返回结果,不符合预期。

因此可以判断的确是事务长时间未结束导致数据库行锁长时间未释放。

为什么forUpdateLockAndRun方法中事务被commit了,但是事务仍然没有结束呢?

查阅源码发现事务的定义private static TransactionDefinition TRANSACTION_DEFINITION = new DefaultTransactionDefinition();,其中使用事务的传播机制默认使用的PROPAGATION_REQUIRED,通过查阅spring-tx中TransactionDefinition的源码中对其的定义如下:

1
2
3
4
5
6
7
/**
* Support a current transaction; create a new one if none exists.
* Analogous to the EJB transaction attribute of the same name.
* <p>This is typically the default setting of a transaction definition,
* and typically defines a transaction synchronization scope.
*/
int PROPAGATION_REQUIRED = 0;

也就是说forUpdateLockAndRun方法中TransactionStatus transactionStatus = TRANSACTION_MANAGER.getTransaction(TRANSACTION_DEFINITION);开启事务的逻辑为:创建一个事务,如果当前已存在事务则加入到这个事务中。

通过查看forUpdateLockAndRun的上层调用链,发现在上层调用链的入口果然也在事务中:

1
2
3
4
5
6
7
8
9
@Transactional(rollbackFor = Exception.class)
public void saveData() {
...

// 下层调用forUpdateLockAndRun
createWybs()

...
}

至此终于找到了SELECT FOR UPDATE慢的原因了:由于forUpdateLockAndRun方法中的SELECT FOR UPDATE必须要等到外层事务结束后才能释放数据库的行锁,因此高并发下请求forUpdateLockAndRun方法就出现了大量排队的情况。

¶解决方案

将事务定义的传播方式设置为PROPAGATION_REQUIRES_NEW:创建一个新事务,如果当前存在事务,则把当前事务挂起,private static TransactionDefinition TRANSACTION_DEFINITION = new DefaultTransactionDefinition(TransactionDefinition.PROPAGATION_REQUIRES_NEW);

调整完成后再次进行压测,结果如下:

类名名 方法名 调用次数 avg min max 90% pct 95% pct 99% pct
LockUtils forUpdateLockAndRun 7248 37 14 523 51 106 271
LockExecutor executeLock 7248 22 6 251 24 51 107

forUpdateLockAndRun的平均耗时从3821ms提升到37ms,性能提升100倍。

注意:将事务定义的传播方式设置为PROPAGATION_REQUIRES_NEW后,锁定的数据修改是单独提交的,如果forUpdateLockAndRun执行成功后(修改已提交到数据库)上层事务处理失败回滚时需要由上层调用方判断forUpdateLockAndRun中修改的数据是否需要回滚,如果需要回滚则需要手动回滚。

如果锁的竞争不是非常大,可以考虑使用乐观锁代替SELECT FOR UPDATE。乐观锁可以通过版本号或时间戳机制来实现并发控制,避免了悲观锁的锁竞争问题。

这里补充一下Spring的7种事务传播机制:

  1. REQUIRED(默认):如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
  2. SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
  3. MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
  4. REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起。
  5. NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
  6. NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
  7. NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于REQUIRED

性能优化-连接池排队问题

发表于 2024-09-17 | 更新于 2026-04-08 | 分类于 性能优化 | 评论数:

¶性能优化-连接池排队问题

数据库连接池中还有很多空闲连接,为什么应用的数据库操作都在排队等待获取和归还连接?

¶背景

生产环境系统上线前进行压测,应用共4个节点,每个节点4核8G,每个节点连接池大小1000,200业务并发(非单接口绝对并发);从监控发现Druid连接池获取连接和释放连接都需要300ms左右的时间,这对于整个系统的吞吐量影响特别大。

某次查询数据库操作中方法调用堆栈耗时监控:
com.alibaba.druid.pool.DruidDataSource.getConnection():耗时327ms
com.oceanbase.jdbc.JDBC4PreparedStatement.execute():耗时3ms
com.alibaba.druid.pool.DruidPooledConnection.close():耗时294ms

¶问题排查过程

¶线程堆栈分析

dump应用的线程信息,发现有217个线程在等待获取数据库连接,有156个线程在等待释放数据库连接,关键线程信息如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
217个线程在等待获取数据库连接
"HSFBizProcessor-DEFAULT-8-thread-446" #1623 daemon prio=10 os_prio=0 tid=0x00007f436c8dc000 nid=0x341154 waiting on condition [0x00007f4310d17000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x000000055d50cf68> (a java.util.concurrent.locks.ReentrantLock$FairSync)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:836)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:897)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1222)
at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335)
at com.alibaba.druid.pool.DruidDataSource.getConnectionInternal(DruidDataSource.java:1632)
...


156个线程在等待释放数据库连接
"HSFBizProcessor-DEFAULT-8-thread-443" #1620 daemon prio=10 os_prio=0 tid=0x00007f436404e000 nid=0x341151 waiting on condition [0x00007f4310e9b000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x000000055d50cf68> (a java.util.concurrent.locks.ReentrantLock$FairSync)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:836)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(AbstractQueuedSynchronizer.java:870)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:1199)
at java.util.concurrent.locks.ReentrantLock$FairSync.lock(ReentrantLock.java:224)
at java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:285)
at com.alibaba.druid.pool.DruidDataSource.recycle(DruidDataSource.java:2016)
...

¶怀疑数据库连接泄露

数据库连接泄露的问题主要有两类:

  1. 手动获取数据库连接,未释放。
  2. 手动开始事务,未结束(提交或回滚)。

全局搜索项目源码,未发现以上两种情况。根据压测结束后堆dump信息(没有开连接池信息打印),从堆信息中发现DruidDataSource对象的池中连接数poolingCount=1000,也就是说所有的连接已全部归还到连接池中。

因此数据库连接泄露的嫌疑被排除。

¶怀疑有慢sql或者有大事务长时间占用连接

根据线程信息分析,正在执行数据库操作com.oceanbase.jdbc.JDBC4PreparedStatement.execute的线程仅仅只有一个。因此有大量慢sql,导致数据库连接池耗尽的嫌疑被排除。

从线程信息中不能直接发现是否有大事务长时间占用连接,根据源码分析又仿佛大海捞针,这里再看看堆dump信息,发现DruidDataSource对象的活跃连接数峰值activePeak=303,也就是说连接池中始终都有空闲可用的连接。因此有大事务长时间占用连接,导致数据库连接池耗尽的嫌疑被排除。

¶怀疑连接池本身的性能问题

从线程信息中发现,所有的获取和释放连接的线程都在等待同一把锁(公平锁):- parking to wait for <0x000000055d50cf68> (a java.util.concurrent.locks.ReentrantLock$FairSync),对应DruidDataSource的关键源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55

private DruidPooledConnection getConnectionInternal(long maxWait) throws SQLException {
...

DruidConnectionHolder holder;
for (boolean createDirect = false;;) {
...
try {
lock.lockInterruptibly();
} catch (InterruptedException e) {
connectErrorCountUpdater.incrementAndGet(this);
throw new SQLException("interrupt", e);
}

try {
...

if (maxWait > 0) {
// 配置了连接获取超时时间
holder = pollLast(nanos);
} else {
// 未配置连接获取超时时间
holder = takeLast();
}
...
}
...
holder.incrementUseCount();

DruidPooledConnection poolalbeConnection = new DruidPooledConnection(holder);
return poolalbeConnection;
}
}

/**
* 回收连接
*/
protected void recycle(DruidPooledConnection pooledConnection) throws SQLException {
...
// 获取连接池的锁,归还holder,将其放入连接池中
lock.lock();
try {
if (holder.active) {
activeCount--;
holder.active = false;
}
closeCount++;

result = putLast(holder, currentTimeMillis);
recycleCount++;
} finally {
lock.unlock();
}
...
}

查阅Druid的文档,发现有介绍Druid锁的公平模式问题:https://github.com/alibaba/druid/wiki/Druid锁的公平模式问题

版本 处理方式 效果
0.2.3之前 unfair 并发性能很好。

maxWait>0的配置下,出现严重不公平现象
0.2.3 ~ 0.2.6 fair 公平,但是并发性能很差
0.2.7 通过构造函数传入参数指定fair或者unfair,缺省fair 按需要配置,但是比较麻烦
0.2.8 缺省unfair,通过构造函数传入参数指定fair或者unfair;

如果DruidDataSource还没有初始化,修改maxWait大于0,自动转换为fair模式
智能配置,能够兼顾性能和公平性

应用确实配置了maxWait参数,从线程信息中看也确实是使用的公平锁ReentrantLock$FairSync,在高并发下性能表现很差。

到此为止,基本可以确定公平锁的并发性能差导致连接池排队等待获取和归还连接问题,下面让我们来验证下公平锁和非公平锁对性能的影响到底有多大。

¶锁的公平模式性能验证

  • 测试接口每次请求中并发查询30次简单sql,具体代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
public String selectTest() {
List<Promise<?>> promises = new ArrayList<>();
for (int i = 0; i < 30; i++) {
Promise<String> promise = CompletableHelper.promise(() -> {
return (String) SqlRunner.db()
.selectObj("select '1' from dual");
});
promises.add(promise);
}
List<?> results = CompletableHelper.waitAll(promises);
return StringUtils.join(results, ",");
}
  • 应用配置:

    • 单节点4核16G
    • JVM内存:-Xms5120m -Xmx5120m -Xmn1706m
  • 压测、监控工具

    • JMeter
    • javaagent:性能优化利器-JavaAgent
  • 压测结果:

锁模式 数据库连接池大小 并发数 获取连接:getConnectionInternal

平均耗时ms
归还连接:recycle

平均耗时ms
连接池最大活跃数:activePeak 接口请求样本数 接口请求响应平均ms 接口请求响应90%百分位ms 接口请求响应最小值ms 接口请求响应最大值ms 接口请求吞吐量
非公平锁 200 100 5 1 200 631733 534 972 7 5689 187.2/sec
公平锁 200 100 107 62 200 292050 959 1324 354 3533 104.1/sec
非公平锁 500 100 1.8 2.2 500 220793 525 950 7 2976 189.0/sec
公平锁 500 100 71 70 500 152745 785 1207 347 5407 127.2/sec
非公平锁 1000 100 1 1.5 510 232726 520 947 8 2547 193.9/sec
公平锁 1000 100 83 82 557 134607 891 1367 68 5440 112.1/sec
  • 压测结论:非公平锁模式下获取和归还连接的性能遥遥领先公平锁模式。

¶解决方案

数据库连接池推荐配置(连接池大小需根据实际情况调整)

  • initialSize:500
  • minIdle:500
  • maxActive:500
  • maxWait:6000
  • keepAlive:true
  • 在连接池初始化之前,手动设置:dataSouce.setUseUnfairLock(true)

更多推荐配置见:https://github.com/alibaba/druid/wiki/DruidDataSource配置

修改Druid连接池配置后,更新应用到生产环境复测(200并发),javaagent监控druid相关方法耗时从几百ms降低到不足个位数,事务操作耗时从平均10s降低到平均2s,至此数据库操作都在排队等待获取和归还连接问题得以解决。

尽管极端情况下,在连接池中的连接不够用大量线程争用连接时,unfair模式的ReentrantLock.tryLock方法存在严重不公的现象,个别线程会等到超时了还获取不到连接。

个人观点:数据库连接池的锁调整为非公平锁整体来看利远大于弊,如果真的有这么大的并发量,更应该增加应用节点数量,缓解单节点的压力。

¶参考文档

  • 性能优化利器-JavaAgent
  • https://github.com/alibaba/druid/wiki/DruidDataSource配置
  • https://github.com/alibaba/druid/wiki/Druid锁的公平模式问题
  • https://jmeter.apache.org/usermanual/component_reference.html#Aggregate_Report

性能优化利器-javaagent

发表于 2024-09-13 | 更新于 2026-04-08 | 分类于 性能优化 | 评论数:

¶性能优化利器-javaagent

你在做性能优化时有没有遇到过,知道某些接口慢但是不知道具体慢在哪里。当然你可能第一时间会想到使用阿里的arthas去trace一下就能看到调用栈上各个方法耗时了;但是在生产环境一般是单次访问不慢,高并发时才慢,再用arthas去trace就监控生产环境就不太现实了。

如果你第一时间想到的是给各个方法调用加上耗时日志打印,然后根据日志再去分析哪里慢,那么你更应该把这篇文章看完!

¶javaagent

接下来给大家介绍的工具叫javaagent,javaagent 是一个简单优雅的 java agent ,利用 java 自带的 instrument 特性+ javassist 字节码编辑技术,实现了无侵入的方法级性能监控。相比于NewRelic或者开源的pinpoint,以及阿里的arthas,本工具主打的是简单,我们只记录每个方法的执行次数和时间,并输出到json格式的日志文件中,然后可以使用配套的agent-analyer进行分析。

集成方式详见:https://github.com/dingjs/javaagent

监控出来的统计结果:

类名 方法名 总时间 总次数 平均数
com.xxx.ClassA methodA 778015748 2052812 379
com.xxx.ClassA methodB 757438182 2052678 369
com.xxx.ClassA methodC 162202981 2052678 79

从这个统计结果来看,方法运行了205万次,平均时间为379ms,看起来不慢。
但是,性能测试的时候发现有的请求响应时间很长,JMeter聚合报告中90%百分位为2356ms,也就是说有10%的请求响应时间是大于等于2356ms。

从统计结果来看平均响应时间都还很不错,但是对于性能优化来说不能直接的找出慢的方法,那么有没有办法能像JMeter聚合报告那样计算出方法执行耗时的百分比统计结果呢,我们继续往下看。

¶T-Digest在线计算

T-Digest是由Ted Dunning提出,旨在以极低的内存开销计算数据流的大致百分位数。不同于传统的精确计算方法要求大量内存来存储所有数据,tdigest通过聪明的数据结构和算法优化,能够在压缩数据到极小内存空间的同时,提供高质量的百分位数估计。

通过对比最终决定采用MergingDigest算法进行方法执行的统计,这里使用简单的demo进行验证:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
public void testMethodPctCounter() {
int compression = 150;
int factor = 5;

final int M = 100;
final List<MergingDigest> mds = new ArrayList<MergingDigest>(M);
final long[] counts = new long[M];
for (int i = 0; i < M; ++i) {
mds.add(new MergingDigest(compression, (factor + 1) * compression, compression));
counts[i] = 0;
}

// Fill all digests with random values (0~100).
final Random random = new Random();

ThreadPoolExecutor executorService = new ThreadPoolExecutor(
100,
100,
0,
TimeUnit.MINUTES,
new ArrayBlockingQueue<Runnable>(1000),
new ThreadPoolExecutor.CallerRunsPolicy());
for (int i = 0; i < 5000000; ++i) {
executorService.execute((new Runnable() {
@Override
public void run() {
for (int j = 0; j < M; ++j) {
MergingDigest md = mds.get(j);
synchronized (md) {
int data = random.nextInt(101);
long start = System.currentTimeMillis();
md.add(data);
counts[j] = counts[j] + (System.currentTimeMillis() - start);
}
}
}
}));
}
while (true) {
if (executorService.getActiveCount() != 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
break;
}
}
executorService.shutdown();

// Output
double[] qArr = new double[]{0.1, 0.2, 0.25, 0.5, 0.75, 0.90, 0.95, 0.99};
System.out.printf("%10s\t", "MIN");
for (int i = 0 ; i < qArr.length; ++i) {
System.out.printf("%4sth pct\t", (int)(qArr[i] * 100));
}
System.out.printf("%10s\t", "MAX");
System.out.printf("%10s\t", "耗时");
System.out.println();
for (int i = 0 ; i < 11; ++i) {
System.out.print("==========\t");
}
System.out.println();


for (int i = 0; i < mds.size(); ++i) {
MergingDigest md = mds.get(i);
System.out.printf("%10.0f\t", md.getMin());
long start = System.currentTimeMillis();
for (double q : qArr) {
System.out.printf("%10.0f\t", md.quantile(q));
}
counts[i] = counts[i] + (System.currentTimeMillis() - start);
System.out.printf("%10.0f\t", md.getMax());
System.out.printf("%10d\t", counts[i]);
System.out.println();
}

}

100个线程并发测试,每个线程往100个不同的Digest中添加5000000个随机数据[0, 100],并计算耗时,jvm内存限制为:-Xms50m -Xmx50m。部分统计结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
       MIN	  10th pct	  20th pct	  25th pct	  50th pct	  75th pct	  90th pct	  95th pct	  99th pct	       MAX	        耗时
========== ========== ========== ========== ========== ========== ========== ========== ========== ========== ==========
0 10 20 25 50 75 90 95 99 100 942
0 10 20 25 50 75 90 95 99 100 869
0 10 20 25 50 75 90 95 99 100 939
0 10 20 25 50 75 90 95 99 100 895
0 10 20 25 50 75 90 95 99 100 942
0 10 20 25 50 75 90 95 99 100 845
0 10 20 25 50 75 90 95 99 100 887
0 10 20 25 50 75 90 95 99 100 898
0 10 20 25 50 75 90 95 99 100 911
0 10 20 25 50 75 90 95 99 100 815
0 10 20 25 50 75 90 95 99 100 762
0 10 20 25 50 75 90 95 99 100 893
0 10 20 25 50 75 90 95 99 100 933
0 10 20 25 50 75 90 95 99 100 866
0 10 20 25 50 75 90 95 99 100 863
0 10 20 25 50 75 90 95 99 100 920
0 10 20 25 50 75 90 95 99 100 830
0 10 20 25 50 75 90 95 99 100 870
0 10 20 25 50 75 90 95 100 100 913

测试结论:从统计值来看,统计结果基本符合预期,并且耗时基本在800ms~1000ms之间,未发生内存溢出,初步评估可以加入到javaagent方法耗时统计中并且对性能影响在可控范围内。

因此fork出https://github.com/dingjs/javaagent仓库添加方法执行的百分比统计功能,配置方式在agent.properties配置文件中添加如下配置:

1
2
3
4
# 是否统计方法执行时间百分比,同JMeter性能测试百分比计算方式,如果开启默认会统计最大值、最小值
agent.log.stat.execute.time=false
# 方法执行时间统计百分比(agent.log.stat.execute.time=true时有效),多选范围[0, 1],例如:0.5,0.9,0.95,0.99
agent.log.stat.execute.time.pct=0.5,0.9,0.95,0.99

最终监控出来的统计结果:

类名 方法名 总时间 总次数 平均数 最小值 最大值 中位数 90th pct 95th pct 99th pct
com.xxx.ClassA methodA 778015748 2052812 379 10 4399 1029 2356 3476 4399
com.xxx.ClassA methodB 757438182 2052678 369 10 4399 1029 2356 3476 4399
com.xxx.ClassA methodC 162202981 2052678 79 10 1499 125 468 1276 1399

最终该修改顺利地合并回https://github.com/dingjs/javaagent仓库,合并请求:https://github.com/dingjs/javaagent/pull/12

至此可以尽情地使用javaagent工具帮助我们快乐地进行性能优化了^_^

¶参考文档

  • https://github.com/dingjs/javaagent
  • https://jmeter.apache.org/usermanual/component_reference.html#Aggregate_Report
  • https://github.com/tdunning/t-digest

算法设计

发表于 2020-08-19 | 更新于 2026-04-08 | 分类于 算法 | 评论数:

算法(Algorithm)是指解题方案的准确而完整的描述,是一系列解决问题的清晰指令,算法代表着用系统的方法描述解决问题的策略机制。也就是说,能够对一定规范的输入,在有限时间内获得所要求的输出。如果一个算法有缺陷,或不适合于某个问题,执行这个算法将不会解决这个问题。不同的算法可能用不同的时间、空间或效率来完成同样的任务。一个算法的优劣可以用空间复杂度与时间复杂度来衡量。

算法.png

阅读全文 »

Eclipse插件修改之旅

发表于 2020-08-16 | 更新于 2026-04-08 | 分类于 开发工具 | 评论数:

大家有没有遇到Eclipse有些插件有问题,但是又不知道怎么修改,接下来就教大家如何修改Eclipse的插件。
Eclispe插件开发.png

阅读全文 »

信息安全技术

发表于 2020-08-16 | 更新于 2026-04-08 | 分类于 系统架构设计 , 读书笔记 | 评论数:

现在计算机已经渗透到各行各业,在使用过程中产生了大量的电子数据,在通信时如何保证传输数据的安全,如何防止人为、病毒、自然灾害对数据的的侵害,信息安全技术显得越发重要。

信息安全.png

阅读全文 »
1234

fanzhongwei

个人经验总结:java、JavaScript、HTML、VUE等等
36 日志
13 分类
53 标签
GitHub E-Mail
© 2019 – 2026 fanzhongwei
由 Hexo 强力驱动 v3.9.0
|
主题 – NexT.Pisces v7.1.0
蜀ICP备17004833号-1
0%