亿级流量点赞系统
快来分享你的内容吧~
- 04-05 14:33
- 2025-06-09·Java后端#提问 亿级流量点赞项目 亿级流量点赞项目,为啥项目拉下来就启动不了,好奇怪啊,这个是lombok报错,但是这些依赖正常都是有的,好奇怪啊,有人遇到同样的问题了吗?编程导航_小y:可以加亿级流量点赞项目交流群:https://www.codefather.cn/course/1912696290659577857?tabKey=qunCode看下这个帖子,是不是可以解决你的问题:https://codefather.cn/post/1864728138141466625110分享
- 2025-05-11·后端开发
- 2025-05-11·后端开发
- 2025-05-10·后端开发
- 2025-05-08·@编程小助手 微信: leikooo_环境准备+ 亿级流量点赞系统源码+ Redis 网盘安装包 提取码: vmty+ MySQL5.7 网盘安装包 提取码:bbr5+ MySQL8.0 网盘安装包提取码:g17s+ MySQL 安装指南+ MySQL 官方下载链接+ Docker Desktop Windows 版本百度网盘+ Java 21安装依赖设置 Maven 完成 https://www.codefather.cn/post查看全文YaeMivo:很棒! 我之前尝试直接 Docker 部署 TiDB, 发现本地 MySQL 一直连不上. 没想到今天无意翻你的帖子发现了解决办法哈哈, 我的文档终于可以进一步完善啦~😆28721分享
📚 学习实战 | 亿级流量高并发点赞系统(Spring Boot3 + Java21 进阶项目落地)
> 最近跟着亿级流量高并发点赞教程系统学习了分布式架构设计,总觉得光看理论不动手实战等于白学,索性就以抢票这个经典的高并发业务场景为目标,把教程里讲到的高可用设计、全链路限流熔断、异步解耦、可观测监控这些核心知识点,完整落地成了一套可直接运行、可压测调优、符合生产规范的微服务抢票系统。 > 从0到1拆分微服务、写核心抢票逻辑、做全链路稳定性防护、压测调优,前前后后打磨了一个多月,把Java 21虚拟线程、Redis Lua原子操作、Kafka消息可靠性保障、Sentinel熔断降级这些秋招面试高频考点都踩了一遍坑,现在把完整项目开源出来,给同样在学后端进阶、准备秋招的同学做个实战参考,也欢迎大家一起交流优化~ ## 💡 项目核心技术栈 完全贴合当下企业主流技术栈,紧跟Java技术前沿: | 核心组件 | 技术选型 | | :--- | :--- | | 基础框架 | Spring Boot 3.2.0 | | JDK版本 | Java 21(虚拟线程) | | 微服务体系 | Spring Cloud Gateway + Nacos + OpenFeign | | 缓存设计 | Caffeine + Redis 7 多级缓存 | | 消息队列 | Kafka 7.5.0 | | 数据库 | MySQL 8.0 + MyBatis-Plus | | 限流熔断 | Alibaba Sentinel | | 定时任务 | XXL-Job | | 监控体系 | Prometheus + Grafana | | 部署方案 | Docker + Kubernetes | | 性能压测 | k6 | ## ✨ 核心落地亮点(教程知识点实战+场景化优化) ### 1. 高并发抢票核心流程:Lua + Kafka + DB 最终一致性 针对抢票最核心的超卖问题,用Redis Lua脚本实现了原子性的库存扣减+限购校验,单脚本内完成所有前置校验和库存操作,避免并发问题;再通过Kafka异步解耦,把订单创建和DB库存扣减异步化,大幅提升抢票接口的吞吐量,同时保证数据最终一致性。 ### 2. 全链路熔断降级与限流防护 跟着教程里的高可用设计思路,实现了4层防护,彻底避免单点故障拖垮整个系统: - API层:基于Sentinel实现用户维度的接口限流,自定义注解开箱即用 - 服务间调用:OpenFeign全量配置降级工厂,服务异常自动降级返回 - 缓存层:Redis所有操作都做了熔断保护,避免缓存故障雪崩 - 消息层:Kafka发送失败实现两层降级(Redis Stream持久化 + 本地内存队列兜底),搭配XXL-Job定时重试,保证消息不丢失 ### 3. 性能优化细节落地 - 多级缓存设计:Caffeine本地缓存 + Redis分布式缓存,网关实现Sticky Session一致性哈希路由,大幅提升本地缓存命中率 - 全面拥抱Java 21虚拟线程,开启Spring Boot虚拟线程支持,大幅提升系统在高并发下的吞吐量 - 缓存失效通过Redis Pub/Sub广播通知,解决多实例本地缓存数据一致性问题 ### 4. 完整的工程化与生产级配套 - 提供一键启动/停止脚本,Docker compose一键拉起所有基础设施,开箱即用 - 完整的Kubernetes生产环境部署配置,包含命名空间、中间件、微服务、HPA自动扩缩容全套配置 - 配套k6性能压测脚本,覆盖登录、查询、抢票全场景,可直接执行压测验证性能 - 集成Prometheus + Grafana全栈监控,配套预定义的监控面板,实时观测系统运行状态 ## ⭐️ 完整源码地址 项目所有代码、配置、脚本都已完整开源,包含详细的文档说明,欢迎 Star 交流,一起优化完善: https://github.com/zunff/ticket-booking-backend 顺手把前端vibe coding了出来 https://github.com/zunff/ticket-booking-frontend
#提问 亿级流量点赞项目 亿级流量点赞项目,为啥项目拉下来就启动不了,好奇怪啊,这个是lombok报错,但是这些依赖正常都是有的,好奇怪啊,有人遇到同样的问题了吗?
亿级流量点赞系统终章
## 项目地址 > GitHub地址💥:https://github.com/tenyon61/likeboom > > 后端初始化模板💥:https://github.com/tenyon61/springboot3-demo/tree/single ## 🌸总结 ### ✨第一章 通过快速启动模板搭建项目,将 jdk 从17升级到21,完成简单的crud功能,解决一些从SpringBoot2.x升级到3版本的依赖报错问题。 ### ✨第二章 - 用Sa-Token对用户进行登录鉴权,简单高效; - 将点赞信息由 MySQL 转存 redis 的 Hash,通过用户 Id批量获取用户点赞博客列表; - 对点赞数据设置过期1月周长,实现数据冷热分离。 ### ✨第三章 - 公司内用`lua`脚本实践不多,补充了`lua`脚本这方面的不足; - 通过 lua 脚本+同步机制+补偿机制实现Redis强一致性和数据库最终一致性。 ### ✨第四章 - 使用`Caffine+Redis+MySQL`实现多级缓存策略,提高系统的稳定性; - 之前了解学习过京东的`hotKey`,本次学习了`HeavyKeeper`实现流量削峰。 ### ✨第五章 - 学习新的消息队列`Pluser`的使用; - 通过对账 Job 的最终一致性兜底,形成了完整的数据一致性解决方案。 ### ✨第六章 - 学习新的数据库`TiDB`的使用; - 将`MySQL`迁移到`TiDB`,增强水平扩展能力。 ### ✨第七章 - 压测是检验系统稳定性必不可少的阶段,通过经典压测工具`Jmeter`对点赞写进行压测; - 比对不同方案下,`TPS`的不同,从而采取最适合的方案; - 学习如何用并行流批量生成`mock`用户。 ### ✨第八章 - 为点赞系统搭建监控平台,搭建 Granfana 作为数据展示平台,应用 Prometheus 作为可观测性解决方案; - 指定合理的阈值,在问题影响面扩大前提前处理。 ### ✨第九章 从理论上学习多种高可用方案,包含数据库高可用、缓存高可用、消息队列高可用,应用层高可用。 ### ✨第十章 从0到1,使用`AI`生成`prompt`,并且通过`Cursor`生成简单h5代码,开发出首页、登录、点赞列表,点赞详情页面。 ## 最后 点赞系统这个项目部分技术栈即使在实际开发业务种并没有使用,但仍可以根据文字教学无损上手实践出来,非常时候摸鱼时候提高自己。 最后的最后,欢迎大家一起来`摸鱼`https://yucoder.cn
亿级流量点赞系统第十章
## 项目地址 > GitHub地址💥:https://github.com/tenyon61/likeboom > > 后端初始化模板💥:https://github.com/tenyon61/springboot3-demo/tree/single ## 快速开始 ### ✨安装Cursor 地址:https://www.cursor.com/cn  - 直接安装 - 一键应用VsCode配置 ### ✨根据Prompt调试  ### ✨生成前端页面 #### 💥首页 ~~~html <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>博客系统</title> <link rel="stylesheet" href="css/style.css"> </head> <body> <div class="container"> <header> <h1>欢迎来到博客系统</h1> </header> <main> <div class="welcome-card"> <h2>请选择以下操作</h2> <div class="button-group"> <a href="login.html" class="btn primary-btn">用户登录</a> <a href="blog-list.html" class="btn secondary-btn">博客列表</a> </div> </div> </main> <footer> <p>© 2023 博客系统 - 版权所有</p> </footer> </div> </body> </html> ~~~ #### 💥博客列表 ~~~html <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>博客列表 - 博客系统</title> <link rel="stylesheet" href="css/style.css"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css"> </head> <body> <div class="container"> <header> <h1>博客列表</h1> <div class="header-right"> <div id="userInfo">未登录</div> <a href="index.html" class="back-link">返回首页</a> </div> </header> <main> <div class="blog-list" id="blogList"> <div class="loading">加载中,请稍后...</div> </div> </main> <footer> <p>© 2023 博客系统 - 版权所有</p> </footer> </div> <script> // 检查登录状态 const currentUser = JSON.parse(localStorage.getItem('currentUser') || 'null'); if (currentUser) { document.getElementById('userInfo').innerHTML = ` <span class="user-welcome">欢迎,${currentUser.username || '用户' + currentUser.id}</span> `; } // 获取博客列表 fetch('http://localhost:8072/api/blog/getBlogVOList') .then(response => response.json()) .then(data => { if (data.code === 0 && data.data) { const blogListElement = document.getElementById('blogList'); blogListElement.innerHTML = ''; if (data.data.length === 0) { blogListElement.innerHTML = '<div class="empty-list">暂无博客内容</div>'; return; } data.data.forEach(blog => { const blogCard = document.createElement('div'); blogCard.className = 'blog-card'; blogCard.innerHTML = ` <div class="blog-card-content"> <div class="blog-cover"> <img src="${blog.coverImg || 'https://via.placeholder.com/300x200/e0e0e0/2c3e50?text=博客封面'}" alt="博客封面" onerror="this.src='https://via.placeholder.com/300x200/e0e0e0/2c3e50?text=无封面'"> </div> <div class="blog-info-container"> <h2 class="blog-title">${blog.title || '无标题'}</h2> <div class="blog-info"> <span>作者: ${blog.authorName || '匿名'}</span> <span>发布时间: ${formatDate(blog.createTime)}</span> </div> <p class="blog-content">${blog.content ? (blog.content.length > 150 ? blog.content.substring(0, 150) + '...' : blog.content) : '无内容'}</p> <div class="blog-actions"> <a href="blog-detail.html?id=${blog.id}" class="btn secondary-btn">查看详情</a> <button class="thumb-btn ${blog.hasThumb ? 'liked' : ''}" data-id="${blog.id}"> <i class="bi bi-hand-thumbs-up${blog.hasThumb ? '-fill' : ''}"></i> <span class="thumb-count">${blog.thumbNum || 0}</span> </button> </div> </div> </div> `; blogListElement.appendChild(blogCard); }); // 添加点赞事件监听 addThumbListeners(); } else { document.getElementById('blogList').innerHTML = `<div class="error">获取博客列表失败:${data.msg || '未知错误'}</div>`; } }) .catch(error => { document.getElementById('blogList').innerHTML = `<div class="error">请求出错:${error.message}</div>`; }); // 格式化日期 function formatDate(timestamp) { if (!timestamp) return '未知时间'; const date = new Date(timestamp); return date.toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' }); } // 添加点赞事件监听 function addThumbListeners() { document.querySelectorAll('.thumb-btn').forEach(btn => { btn.addEventListener('click', function () { // 检查是否登录 if (!currentUser) { alert('请先登录再进行点赞操作'); window.location.href = 'login.html'; return; } const blogId = this.dataset.id; const isLiked = this.classList.contains('liked'); const endpoint = isLiked ? 'undo' : 'do'; // 构建请求数据 const requestData = { entityId: blogId, entityType: 'blog' // 假设后端有这个类型 }; // 发送点赞/取消点赞请求 fetch(`http://localhost:8072/api/thumb/${endpoint}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(requestData) }) .then(response => response.json()) .then(data => { if (data.code === 0 && data.data) { // 更新UI const countElem = this.querySelector('.thumb-count'); const iconElem = this.querySelector('i'); if (isLiked) { this.classList.remove('liked'); iconElem.className = 'bi bi-hand-thumbs-up'; countElem.textContent = parseInt(countElem.textContent) - 1; } else { this.classList.add('liked'); iconElem.className = 'bi bi-hand-thumbs-up-fill'; countElem.textContent = parseInt(countElem.textContent) + 1; } } else { alert(`操作失败:${data.msg || '未知错误'}`); } }) .catch(error => { alert(`请求出错:${error.message}`); }); }); }); } </script> </body> </html> ~~~ #### 💥生成结果  ## 本章总结 通过AI生成`prompt`,并且使用`Cursor`生成简单h5代码。 ## 项目踩坑 待补充
亿级流量点赞系统第九章
## 项目地址 > GitHub地址💥:https://github.com/tenyon61/likeboom > > 后端初始化模板💥:https://github.com/tenyon61/springboot3-demo/tree/single ## 快速开始 ### ✨高可用方案 - 数据库高可用 - 缓存高可用 - 消息队列高可用 - 应用层高可用 ### ✨数据库高可用 `TiDB`作为分布式数据库已具备基础的高可用,可以通过 - 多中心部署 - 两地三中心架构 在不同地区部署`TiDB`集群 ### ✨缓存高可用 `Redis`高可用三种实现 1. 主从复制 - 一主多从架构,主节点写,从节点读 - 读写分离,减少主节点压力 1. 哨兵模式 - 哨兵节点监控redis实例健康状态 - 自动监测主节点失败并执行故障转移 1. Redis Cluster - 数据自动分片,每个分片有主从结构 - 去中心化架构,无单点故障 ### ✨消息队列高可用 本次使用的是`pluser`,天生支持地理复制和多租户,可实现跨区域容灾 - `pluser`不可用时就降级,将异步点赞转为同步处理 - `pluser`不可用时就降级,用`redis`暂存数据,同步任务写入`MySQL` ### ✨应用层容错 `Sentinel`实现针对外部依赖的服务调用熔断保护,其可以 - 接口级限流:控制单个接口的请求频率 - 用户级限流:限制单个用户的操作频率 - 系统级限流:根据负载动态调整总体接受请求量 ### ✨增加监控 - 硬件监控 - 中间件监控 - 应用监控 - 通过`Prometheus`采集,实时通知运维人员 ## 本章总结 学习多种高可用方案,包含数据库高可用、缓存高可用、消息队列高可用,应用层高可用。 ## 项目踩坑 待补充
亿级流量点赞系统第八章
## 项目地址 > GitHub地址💥:https://github.com/tenyon61/likeboom > > 后端初始化模板💥:https://github.com/tenyon61/springboot3-demo/tree/single ## 快速开始 ### ✨引入依赖 ~~~xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-registry-prometheus</artifactId> </dependency> ~~~ ~~~yaml management: endpoints: web: exposure: include: health, prometheus metrics: distribution: percentiles: http: server: requests: 0.5, 0.75, 0.9, 0.95, 0.99 ~~~ ### ✨代码改造 `ThumbController.java` ~~~java private final Counter successCounter; private final Counter failureCounter; public ThumbController(MeterRegistry registry) { this.successCounter = Counter.builder("thumb.success.count") .description("Total sucessful thumb") .register(registry); this.failureCounter = Counter.builder("thumb.failure.count") .description("Total failed thumb") .register(registry); } ~~~ ### ✨重启TiDB 执行命令 ~~~shell tiup playground --tag thumb --without-monitor --db.host 0.0.0.0 ~~~ 成功执行  ### ✨Redis指标监控 用`Redis Exporter`监控`reids` ~~~shell docker run --name redis-exporter \ -p 9121:9121 \ oliver006/redis_exporter \ --redis.addr=redis://IP:6379 \ --redis.password=pwd ~~~ 执行成功  ### ✨安装Prometheus 创建`prometheus.yml`,放到`/opt/prometheus`下面 ~~~yaml global: scrape_interval: 15s # By default, scrape targets every 15 seconds. # Attach these labels to any time series or alerts when communicating with # external systems (federation, remote storage, Alertmanager). external_labels: monitor: 'codelab-monitor' # A scrape configuration containing exactly one endpoint to scrape: # Here it's Prometheus itself. scrape_configs: # The job name is added as a label `job=<job_name>` to any timeseries scraped from this config. - job_name: 'prometheus' # Override the global default and scrape targets from this job every 5 seconds. scrape_interval: 5s static_configs: - targets: ['localhost:9090'] - job_name: 'biz' # Override the global default and scrape targets from this job every 5 seconds. scrape_interval: 5s metrics_path: '/api/actuator/prometheus' static_configs: - targets: ['IP:9199'] labels: group: 'thumb' - job_name: 'redis' scrape_interval: 5s static_configs: - targets: ['IP:9121'] ~~~ 执行启动命令 ~~~shell docker run -d \ --name prometheus \ -p 9199:9090 \ -v /opt/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml \ prom/prometheus ~~~ 安装成功  ### ✨启动Grafana ~~~shell docker run -d -p 3000:3000 --name=grafana \ --user "$(id -u)" \ --volume "$PWD/data:/var/lib/grafana" \ grafana/grafana-enterprise ~~~ 访问`IP:3000`admin/admin即可访问  #### 💥配置Prometheus 选择Prometheus  #### 💥连接数据源 连接成功  #### 💥配置各项指标 进入控制面板  给出一些常用配置: - QPS ~~~shell sum(rate(http_server_requests_seconds_count[1m])) ~~~ 由于是在公网部署的`prometheus.yml`中定义的biz连接不了本地,因此上面的数据面板是空的,当部署上线后即可看到。  ## 本章总结 搭建 Granfana 作为数据展示平台,应用 Prometheus 作为可观测性解决方案。 ## 项目踩坑 待补充
亿级流量点赞系统第七章
## 项目地址 > GitHub地址💥:https://github.com/tenyon61/likeboom > > 后端初始化模板💥:https://github.com/tenyon61/springboot3-demo/tree/single ## 快速开始 ### ✨安装JMeter 地址:https://jmeter.apache.org/download_jmeter.cgi  ### ✨压测配置 修改`bin\jmeter.properties`的`language`改为`zh_CN` 点击`jmeter.bat`启动`JMeter` - 添加线程组 - 添加信息头管理器 - 添加HTTP请求 - 添加响应断言 最后基本配置如下  首先测试能否调通  ### ✨压测流程 批量插入5w人数据  #### 💥并行流获取SESSION ~~~java @Test void testLoginAndExportSessionToCsv() throws Exception { List<User> list = userService.list(); try (PrintWriter writer = new PrintWriter(new FileWriter("session_output.csv", true))) { writer.println("userId,sessionId,timestamp"); // 使用并行流处理用户登录 list.parallelStream().forEach(user -> { try { long testUserId = user.getId(); MvcResult result = mockMvc.perform(get("/api/auth/login") .param("userId", String.valueOf(testUserId)) .contentType(MediaType.APPLICATION_JSON)) .andReturn(); List<String> setCookieHeaders = result.getResponse().getHeaders("Set-Cookie"); assertThat(setCookieHeaders).isNotEmpty(); String sessionId = setCookieHeaders.stream() .filter(cookie -> cookie.startsWith("SESSION")) .map(cookie -> cookie.split(";")[0]) .findFirst() .orElseThrow(() -> new RuntimeException("No SESSION found in response")); String sessionValue = sessionId.split("=")[1]; // 使用同步块保证线程安全 synchronized (writer) { writer.printf("%d,%s,%s%n", testUserId, sessionValue, LocalDateTime.now()); } System.out.println("✅ 写入 CSV:" + testUserId + " -> " + sessionValue); } catch (Exception e) { e.printStackTrace(); } }); } } ~~~ 拿到csv  #### 💥读取CSV  #### 💥配置sessionId  #### 💥压测结果 以`Redis + 同步任务`为例 1.单压测`Redis`写  2.`Redis+同步任务`  ## 本章总结 对于使用`Apache JMeter`对点赞系统压测,通过摸拟真实用户请求的方式,来实现高并发测试。 ## 项目踩坑 - 不同压测需要切换提交记录
亿级流量点赞系统项目学习总结
# ✨亿级流量点赞项目总结 刚刚研究生上岸,回归编程导航继续学习技术,考虑到差不多有半年没写代码了,不过一直想找个项目来做一次康复训练,嘿嘿,那就不得不出手了。冲冲冲!🚀🚀🚀 ## ✨项目代码 GitHub 地址🪄 后端:https://github.com/FangMoyu/thunder-like 前端:https://github.com/FangMoyu/thunder-like-frontend ## ✨项目总结 ### 第一章 第一章非常的友好啊!基本的代码环境搭建,对于我这种长时间没有写代码的人来说确实是一个很好的康复训练,跟着教程写完代码逐渐想起来了项目搭建的基本流程和很多基本语法。还通过 AI 了解了编程式事务,学到了一些新知识。 ### 第二章 第二章的学习也没有很大的压力,很好地复习了 Spring Data Redis 的一些 API以及 Redis 的哈希存储结构。除了直接用缓存存储之外,还学习到了定时任务补偿、Lua 脚本确保原子性、@Service 指定 value 来注入不同的对象等。 ### 第三章 了解了多级缓存架构、HeavyKeeper 算法原理以及实现热点 Key 检测。 ### 第四章 我是初次使用 docker,由于我的电脑是 Windows 系统,在使用时还是出现了许多 Bug 的,在控制台安装 Pulsar 时一直报错,后来才知道要把所有 `\` 改成 `^` 才能运行: ```bash docker run -it ^ -p 6650:6650 ^ -p 8080:8080 ^ --mount source=pulsardata,target=/pulsar/data ^ --mount source=pulsarconf,target=/pulsar/conf ^ apachepulsar/pulsar:4.0.3 ^ bin/pulsar standalone ``` 本章学到了 Pulsar 消息队列的功能和使用确保消息可靠性的方案:重试、私信队列和消息一致性的方案:设置对账任务。 ### 第五章 **部署分布式数据库 TiDB** 学习了如何部署分布式数据库 TiDB,本章也是各种 Bug 频出,在 VMware 虚拟机一顿折腾,废了好大劲才安装好 TiDB。幸好有厉害的鱼友分享了 Windows 远程访问 TiDB 的方案,让我顺利完成了部署。 ### 第六章 **压力测试,数据监控** 学习了压力测试、性能监控、搭建数据可视化平台 Granfana 来实时监控数据等。 ### 第七章 学习到了很多优化设计系统高可用性的方案,虽然教程并没有做具体实现,但是也了解了很多高可用的方案,比如数据库、缓存、消息队列的高可用方案。 ### 第八章 不得不说,AI 真的带来了巨大的便利啊!在学习使用 Cursor 搭建前端项目时,我深深体会到了对于一个前端小白来说,AI 真的宛如救星,整个前端搭建的过程中我没有编写一行代码,出现 Bug 的时候通过向 AI 简单地描述它也能较好地解决问题。 ## 总结 第一次学习纯文字教程的项目,这个项目对我来说还是有难度的(有几个晚上为了修 bug 肝到2点了😭)本来觉得可能会完不成,但是编程导航的小编们把这个项目教程写得如此详实同时再加上鱼友们的笔记,让我这个很久没写代码的小白也能快速上手并完成项目,学习了很多知识,整体来说收获颇丰!希望能和大家一起加油鸭🚀🚀🚀
扩展-点赞系统笔记-理解分位值的含义
在 [亿级流量点赞系统教程](https://www.codefather.cn/course/1912696290659577857/section/1912699417875550210?type=#heading-27)中系统可观测控制面板中配置分位值,看教程中的解释不是很理解,在学习一下。 <img src="https://pic.code-nav.cn/post_picture/1804737267763691522/YHkHUyi1eV2ubTSM.webp" alt="image.png" width="100%" /> # 分位值理解 分位值(Quantile)是一种统计学概念,用于描述数据分布的特征。它将一组数据按照从小到大的顺序排列,并将其分成若干等份,每个等份的边界值即为分位值。在性能分析中,分位值常用来评估系统的响应时间、延迟等指标,特别是当数据集中存在异常值或极端值时,分位值能更准确地反映大多数请求的真实情况。 以教程中提到的90分位值(P90)为例,假设我们有一段时间内收集了100个请求的响应时间数据,我们将这些响应时间从小到大排序。排在第90位的响应时间就是这组数据的90分位值(P90)。这意味着在这100个请求中,有90%的请求响应时间小于或等于这个值,只有10%的请求响应时间大于这个值。 具体来说,如果这100个请求的响应时间分别为: [ t_1, t_2, ..., t_100] ,且已经按从小到大的顺序排列,那么P90就是t_90。 例如,假设这100个请求的响应时间(单位:毫秒)如下: [ 50, 60, 70, ..., 140, 150, 160, 170, 180, 190, 200, 300, 400, 500, 600, 700, 800, 900, 1000, 1500 ] 其中前90个请求的响应时间都在200毫秒以内,而最后10个请求的响应时间则显著增加。在这种情况下,P90将是200毫秒,表示90%的请求响应时间不超过200毫秒。这样,即使有少数极慢的请求(如1500毫秒),也不会对整体性能评估产生过大影响。 通过使用分位值,可以更好地理解系统在大部分情况下的表现,而不是被少数异常值所误导。 常见的分位值包括P50(中位数)、P95和P99等,它们分别表示50%、95%和99%的请求响应时间低于该值。 # 分位值实战 找张图理解下: <img src="https://pic.code-nav.cn/post_picture/1804737267763691522/xN4Kb8B8NyNrkeIt.png" alt="image.png" width="139px" /> ## 图中百分位的理解 P50(中位数): 这是数据的中位数值。一半的数据点低于这个值,另一半高于它。图中,P50的值为40毫秒。这意味着50%的请求响应时间不超过40毫秒。 P75: 表示第75百分位。75%的数据点低于这个值。这里P75的值为120毫秒,表示75%的请求响应时间不超过120毫秒。 P90: 表示第90百分位。90%的数据点低于这个值。P90的值为300毫秒,意味着90%的请求响应时间不超过300毫秒。 P95: 表示第95百分位。95%的数据点低于这个值。P95的值为13,200毫秒,这表明95%的请求响应时间不超过13,200毫秒。这个值显著高于之前的百分位值,说明有少数请求的响应时间非常长。 P99: 表示第99百分位。99%的数据点低于这个值。P99的值为28,150毫秒,意味着99%的请求响应时间不超过28,150毫秒。这个值更高,表明极少数请求经历了极其漫长的响应时间。 ## 观察 图中显示了性能随时间的变化,P95和P99值在某些时刻显著上升。这可能表明系统在这些时间段内负载较高、存在系统问题或其他因素导致延迟。 P90与P95/P99值之间的巨大差异表明,虽然大多数请求表现良好(响应时间在300毫秒以内),但一小部分请求经历了更长的延迟,这可能是需要关注的问题。 ## 影响 对于用户体验至关重要的应用,改善P95和P99值可以显著提升整体满意度,因为它们代表用户可能遇到的最差情况。 随时间监控和分析这些百分位值有助于识别趋势、潜在瓶颈和系统性能优化的领域。
亿级流量点赞系统本地运行
# 环境准备 + [亿级流量点赞系统源码](https://www.codefather.cn/course/1912696290659577857/section/1912755272115859458) + Redis [网盘安装包](https://pan.baidu.com/s/1-QhCv0Crg2zvwG3ODxvv-Q) 提取码: vmty + MySQL5.7 [网盘安装包](https://pan.baidu.com/s/1uIDAzBtxBXHq8YZOfhlI9w?pwd=bbr5) 提取码:bbr5 + MySQL8.0 [网盘安装包](https://pan.baidu.com/s/1O6TrRCpb66A5hdgy0EY9HA?pwd=g17s)提取码:g17s + MySQL [安装指南](https://zhuanlan.zhihu.com/p/37152572) + MySQL [官方下载链接](https://dev.mysql.com/downloads/windows/installer/5.7.html) + Docker Desktop [Windows 版本百度网盘]( https://pan.baidu.com/s/1rB1e3di-O6dI94KNEciywA?pwd=xj2b) + **Java 21** # 安装依赖 > 设置 Maven 完成 https://www.codefather.cn/post/1836689783992958977 使用 IDEA 打开项目 <img src="https://pic.code-nav.cn/post_picture/1608460212774109186/liUQsqPDJp8Y1Kts.webp" alt="image-20241030153859685" width="100%" /> # 选择 Java 版本  选择 **Java 21**  没有的话可以使用下面的 `Download JDK`  选择 **Java 21**  + 本地没有 JDK 可以直接 `Download JDK` + 本地有 JDK 但是没识别到 `Add JDK from disk` 选择对应目录即可 + 选择 Java 21 # 初始化数据 连接本地 MySQL   > 点击 Download 下载 driver files  输入账号密码之后点击`Test Connection` 成功之后直接点击 `OK`  找到根目录的 `sql/create_table.sql`   执行成功后  # 安装 Docker Desktop 从官网 https://www.docker.com 下载对应自己系统版本的 Docker Desktop  双击下载下来的 `Docker Desktop Installer.exe` -> 点击 `OK` 之后就安装成功  # 相关环境 ## pulsar 1、打开安装好的 `Docker Desktop` 2、打开 `Terminal`  3、输入下面的命令 ``` docker run hello-world ```  4、确保 hello-world 跑通,再运行 ``` docker run -d -it --name pulsar -p 6650:6650 -p 8080:8080 docker.1panel.live/apachepulsar/pulsar:4.0.3 bin/pulsar standalone ```  5、启动成功 启动成功之后可与在 `Containers` 里面找到正在运行的 `pulsar`  6、校验是否启动成功 ``` curl http://localhost:8080/admin/v2/brokers/health ``` 会有返回值 `OK` 有的话就启动成功🤩  --- ### BUG 1、如果遇到 `Error response from daemon: Ports are not available: exposing port TCP 0.0.0.0:6650 -> 127.0.0.1:0: listen tcp 0.0.0.0:6650: bind: An attempt was made to access a socket in a way forbidden by its access permissions.****` 1)把 `winnat` 关掉 (PS 需要使用**管理员权限**)   ``` net stop winnat ``` 2)再次执行 docker run ``` docker run -d -it --name pulsar -p 6650:6650 -p 8080:8080 docker.1panel.live/apachepulsar/pulsar:4.0.3 bin/pulsar standalone ``` 3)最后可以再次开启 winnat ``` net start winnat ``` 2、解决办法:不限制 pulsar 的内存,我出现这个 BUG 的场景是限制内存导致的 ``` Caused by: javax.ws.rs.InternalServerErrorException: HTTP 500 {"reason":"\n --- An unexpected error occurred in the server ---\n\nMessage: Topic creation encountered an exception by initialize topic policies service. topic_name=persistent://public/functions/assignments error_message={\"errorMsg\":\"Bookie handle is not available - ledger=2 - operation=Failed to read entry - entry=0\",\"reqId\":845559152753469356, \"remote\":\"localhost/127.0.0.1:6650\", \"local\":\"/127.0.0.1:47776\"}\n\nStacktrace:\n\norg.apache.pulsar.broker.service.BrokerServiceException$ServiceUnitNotReadyException: Topic creation encountered an exception by initialize topic policies service. topic_name=persistent://public/functions/assignments error_message={\"errorMsg\":\"Bookie handle ``` ## TiDB ### 编写 Docker-Compose 1、首先复制下面的内容到任意目录(无中文、无空格)的路径下命名为 `docker-compose.yml` ```yml version: "3.8" networks: backend: driver: bridge services: tidb: image: docker.1panel.live/pingcap/tidb:latest container_name: tidb ports: - "4000:4000" # TiDB MySQL 协议端口 - "10080:10080" # TiDB HTTP 状态端口 networks: - backend mysql: image: docker.1panel.live/library/mysql:8.0 container_name: mysql environment: MYSQL_ROOT_PASSWORD: rootpwd ports: - "3307:3307" # 为了防止和本地 MySQL 端口使用了 3307 冲突 networks: - backend depends_on: - tidb ```  2、打开 `docker desktop` 的 `Terminal`  进入到对应的目录(看自己的 docker-compose.yml 具体的路径)  执行 ``` docker-compose up -d ``` 成功后的效果  3、验证是否启动成功,可以在 containers 里面找到启动成功  3、使用 docker-desktop 进入到 MySQL 之中  选择 Exec  输入下面命令连接 TiDB,可以发现连接成功🤩🤩🤩 ``` mysql -h tidb -P 4000 -uroot ```  ### 设置远程连接用户 登录到 TiDB(用 root) ``` mysql -h tidb -P 4000 -uroot ``` 在 SQL 提示符下,执行(假设你希望密码为`YourPass123`): ``` -- 1) 创建用户(如果已存在可改用 ALTER USER) CREATE USER IF NOT EXISTS 'leikooo'@'%' IDENTIFIED BY 'YourPass123'; -- 2) 授权:给这个用户对所有库的所有表的所有权限 GRANT ALL PRIVILEGES ON *.* TO 'leikooo'@'%'; -- 3) 刷新权限(TiDB 会自动生效,但加上也无妨) FLUSH PRIVILEGES; ```  退出,再用新用户+密码连接: > 退出 TiDB 输入 exit 即可 ``` mysql -h tidb -P4000 -u leikooo -pYourPass123 ``` > 这里 YourPass123 就是设置的密码  ### IDEA 尝试连接 使用 IDEA 自带的 Data Sources 进行尝试链接  注意⭐ 端口是 **4000** !  6、至此完成,**服务器上安装 TiDB 也是同理** ### 迁移 MySQL 数据 1、导出 MySQL 的数据  选择自己 myqldump.exe 的位置  > 如果不知道的话可以下载「[Everything](https://www.voidtools.com/)」 > >  生成成功之后会有弹窗,点击就可以直接到对应文件  2、导入数据到 TiDB 1)首先手动创建数据库 `thumb_db` ``` -- 创建库 create database if not exists thumb_db; ```  创建成功  2)插入数据,选择刚才生成的 sql  选择对应路径然后点击 OK 即可  执行成功   ## 可观测相关 ### Redis 指标监控 1) redis.addr 如果是本地一般是 redis://host.docker.internal:6379 2)如果 redis 没有设置密码那么可以去掉 --redis.password=xxx ```dockerfile docker run --name redis-exporter -d -p 9121:9121 docker.1panel.live/oliver006/redis_exporter --redis.addr=redis://host.docker.internal:6379 --redis.password=xxx ``` > 这里直接指定了前缀去 docker.1panel.live 镜像拉取 oliver006/redis_exporter 加快下载速度  2、启动成功之后可以在 containers 里面找到 `redis-exporter`  3、测试效果 访问:http://localhost:9121  ### prometheus 1、创建 prometheus.yml 这个文件 1)`host.docker.internal:9199` 表示请求宿主机自己的服务的后端,如果部署到服务器上就改成服务器对应的地址 2)`host.docker.internal:9121` 表示请求宿主机自己的服务的 Redis ,如果是服务器就改成服务器对应的地址 ```yml global: scrape_interval: 15s # By default, scrape targets every 15 seconds. # Attach these labels to any time series or alerts when communicating with # external systems (federation, remote storage, Alertmanager). external_labels: monitor: 'codelab-monitor' # A scrape configuration containing exactly one endpoint to scrape: # Here it's Prometheus itself. scrape_configs: # The job name is added as a label `job=<job_name>` to any timeseries scraped from this config. - job_name: 'prometheus' # Override the global default and scrape targets from this job every 5 seconds. scrape_interval: 5s static_configs: - targets: ['localhost:9090'] - job_name: 'biz' # Override the global default and scrape targets from this job every 5 seconds. scrape_interval: 5s metrics_path: '/api/actuator/prometheus' static_configs: - targets: ['host.docker.internal:9199'] labels: group: 'thumb' - job_name: 'redis' scrape_interval: 5s static_configs: - targets: ['host.docker.internal:9121'] ``` 2、把上面的文件复制到桌面上(或者任意非中文、无空格的目录上即可)  3、启动 prometheus 1)进入到包含 `prometheus.yml` 的路径下(输入 ls 命令如下图所示)  2)输入下面命令 Linux\Mac 可以这样写 ``` docker run -d --name prometheus -p 9090:9090 -v ./prometheus.yml:/etc/prometheus/prometheus.yml docker.1panel.live/prom/prometheus ``` Windows 需要写 prometheus.yml 的绝对路径(路径需要参考自己实际的路径) ``` docker run -d --name prometheus -p 9090:9090 -v C:\Users\leikooo\desktop\prometheus.yml:/etc/prometheus/prometheus.yml docker.1panel.live/prom/prometheus ```  4、查看效果 访问:http://localhost:9090  > 如果有 DOWN 的情况就确保 > > 1)启动了 Redis 指标监控 > > 2)启动了后端服务且后端端口是 9199 ### grafana 1、打开 docker desktop 的 Terminal 运行下面的代码 ``` docker run -d --name grafana -p 3000:3000 docker.1panel.live/grafana/grafana-enterprise ``` 2、查看效果 访问:http://localhost:3000/login 默认账号密码都是:admin 官方文档:https://grafana.com/docs/grafana/latest/setup-grafana/sign-in-to-grafana/  剩下的添加数据源的操作可以参考[「9-架构优化:系统可观测性优化」](https://www.codefather.cn/course/1912696290659577857/section/1912699417875550210?type=#heading-23) ## 环境内存占用参考 > 当然可以进行限制内存大小 ``` CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS bacc0434d260 prometheus 0.04% 69.4MiB / 7.567GiB 0.90% 8.53MB / 12.4MB 0B / 0B 26 d407016ca4ef pulsar 1.74% 2.364GiB / 7.567GiB 31.24% 35.7kB / 28.1kB 0B / 0B 344 82b3bb45428e grafana 0.19% 114.9MiB / 7.567GiB 1.48% 22.9MB / 17.9MB 0B / 0B 24 d25e5e2665af redis-exporter 0.00% 17.88MiB / 7.567GiB 0.23% 196kB / 890kB 0B / 0B 15 3082643eff7b mysql 0.35% 370.9MiB / 7.567GiB 4.79% 1.22kB / 126B 0B / 0B 37 73f86948e25e tidb 1.95% 445.4MiB / 7.567GiB 5.75% 426kB / 427kB 0B / 0B 19 ``` > MySQL 后面可以删除,但是删除之后就无法操作  # 修改配置文件 如果要使用 TiDB 的话 修改为对应的 TiDB 连接地址和端口,例如 `jdbc:mysql://127.0.0.1:4000/thumb_db` 如果使用 MySQL 修改为对应的 MySQL 连接地址和端口,例如 jdbc:mysql://127.0.0.1:3306/thumb_db ```yml spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:4000/thumb_db username: root password: 123456 ``` # 启动 1、最终启动 + 启动 pulsar + 启动 TiDB + 可观测相关的不启动也可以运行成功  2、打开接口文档 http://localhost:9199/api/doc.html 进行测试  测试点赞 
