使用go和消息队列优化投票功能

文章目录

    • 1、优化方案与主要实现代码
      • 1.1、原系统的技术架构
      • 1.2、新系统的技术架构
      • 1.3、查看和投票接口实现
      • 1.4、数据入库MySQL协程实现
      • 1.5、路由配置
      • 1.6、启动程序入口实现
    • 2、压测结果
      • 2.1、设置Jmeter线程组
      • 2.2、Jmeter聚合报告结果,支持11240/秒吞吐量
      • 2.3、Jmeter TPS结果,支持15000/秒最大并发
      • 2.4、总CPU和总内存变化情况
      • 2.5、Redis和go进程占用资源

1、优化方案与主要实现代码

有一个每年都举行的投票活动,原系统是很多年前开发,系统的支持的并发数不高,在投票期间经常出现崩掉的情况。
投票规则为按IP限制,每24小时投1票。

1.1、原系统的技术架构

运行在4核8G服务器上,用了PHP+MySQL+Redis开发,运行在4核8G的服务器上。
投票页面的功能很简单:

  • 1、是投票页面的访问,涉及当前选项的投票结果显示;
  • 2、用户点击按钮进行投票,涉及数据入库保存和投票结果刷新问题。

旧投票系统虽然都用了缓存(有缓存时间),但是在持续流量下,缓存被击穿,访问页面或点击投票,出现数据库被读写的情况,系统崩掉。

1.2、新系统的技术架构

使用Go(gin、sqlx、go-redis)+Redis缓存+Redis队列+MySQL
实现逻辑图如下:
在这里插入图片描述

Jmeter压测投票接口:吞吐量在11240/秒,TPS最大值是大于15000/秒(压测结果在后面截图)。

1.3、查看和投票接口实现

查看接口/view和投票接口/vote接口实现

package controllersimport ("encoding/json""fmt""go-vote/config""go-vote/models""go-vote/utils""math/rand""strconv""time""github.com/gin-gonic/gin""github.com/redis/go-redis/v9"
)var redis_util utils.RedisUtilfunc init() {redis_util = utils.RedisUtil{Url: config.Cache.Url, Password: config.Cache.Password}redis_util.Connect()
}
/**
投票记录接口*/
func View(c *gin.Context) {ip := c.ClientIP()// 获取已投票选项city_id, _ := getVotedId(ip)data_count := getVoteCount()// 检查投票是否入库is_vote := isVoteComplete(ip)if is_vote == false {data_count[city_id] = data_count[city_id] + 1}c.JSON(200, gin.H{"code":       200,"city_id":    city_id,"data_count": data_count,})
}
/*
用户提交投票
*/
func Vote(c *gin.Context) {ip := c.ClientIP()// 获取用户本次提交的选项city_idstr := c.PostForm("id")vote_id, err_int := strconv.Atoi(city_idstr)if err_int != nil {c.JSON(200, gin.H{"code":    201,"message": "id格式错误",})}// 获取投票记录value, err2 := getVotedId(ip)if err2 == redis.Nil || value == "" {setVoteId(ip, vote_id)// 标记投票未入库setVoteComplete(ip)// 添加到队列addQueues(ip, vote_id)c.JSON(200, gin.H{"code":    200,"message": "成功",})} else {c.JSON(200, gin.H{"code":    400,"message": "失败",})}
}func getVotedId(ip string) (string, error) {return redis_util.Get(ip)
}func setVoteId(ip string, id int) error {return redis_util.Set(ip, id, time.Hour*24)
}func isVoteComplete(ip string) bool {key := "complete_" + ipvalue, err := redis_util.Get(key)if err == redis.Nil || value != "n" {return true} else {return false}
}
func setVoteComplete(ip string) {key := "complete_" + ipredis_util.Set(key, "n", time.Hour*24)
}
func getVoteCount() map[string]int64 {filename := "city_count.json"count_json, err_read := utils.ReadFile(filename)var total_count map[string]int64if err_read == nil {if err_json := json.Unmarshal([]byte(count_json), &total_count); err_json != nil {panic(err_json)}} else {total_count = make(map[string]int64)}return total_count
}
func addQueues(ip string, id int) {vote_data := models.VoteData{ip, id, time.Now().Format("2006-01-02 15:04:05")}json_data, _ := json.Marshal(vote_data)err := redis_util.LPush("vote_topic", string(json_data))if err != nil {fmt.Println("addQueues======= err:", err)}
}

1.4、数据入库MySQL协程实现

package servicesimport ("encoding/json""fmt""go-vote/config""go-vote/models""go-vote/utils""strconv""time""github.com/redis/go-redis/v9"
)var redis_util utils.RedisUtil
var sqldao utils.SqlDaofunc init() {redis_util = utils.RedisUtil{Url:      config.Cache.Url,Password: config.Cache.Password,}redis_util.Connect()sqldao = utils.SqlDao{Driver: config.Db.Driver,Dsn:    config.Db.Dsn,}sqldao.Connect()
}func VoteJob() {layout := "2006-01-02 15:04:05"layout2 := "20060102"shanghaiZone, _ := time.LoadLocation("Asia/Shanghai")for {var list_data []interface{}for {value, err := redis_util.LPop("vote_topic").Result()if err == nil && value != "" {data := models.VoteData{}json.Unmarshal([]byte(value), &data)create_date, _ := time.ParseInLocation(layout, data.Date, shanghaiZone)create_day, _ := strconv.Atoi(create_date.Format(layout2))vote_log := models.VoteLog{CityId: data.Id, ClientIp: data.Ip, CreateDay: create_day, CreateDate: create_date}list_data = append(list_data, vote_log)if len(list_data) >= 1000 {saveLog(list_data)list_data = []interface{}{}}} else if err == redis.Nil {fmt.Println("break=======", time.Now().Format("2006-01-02 15:04:05"))break}}if len(list_data) > 0 {saveLog(list_data)list_data = []interface{}{}}time.Sleep(time.Second * 1)}
}
func saveLog(list_data []interface{}) {new_count := make(map[int]int64)count, err_insert := sqldao.InsertManyObj("insert into vote_log(city_id,client_ip,create_day,create_date) values(:city_id,:client_ip,:create_day,:create_date)", list_data)for _, v := range list_data {data := v.(models.VoteLog)city_id := data.CityIdcity_count, ok := new_count[city_id]if ok == false {new_count[city_id] = 1} else {new_count[city_id] = city_count + 1}client_ip := data.ClientIpdelVoteComplete(client_ip)}filename := "city_count.json"count_json, err_read := utils.ReadFile(filename)var total_count map[string]int64if err_read == nil {if err_json := json.Unmarshal([]byte(count_json), &total_count); err_json != nil {panic(err_json)}} else {total_count = make(map[string]int64)}for k, v := range new_count {key := strconv.Itoa(k)count_total, ok := total_count[key]if ok == true {total_count[key] = count_total + v} else {total_count[key] = v}}datas_json, _ := json.Marshal(total_count)utils.WriteFile(filename, datas_json)
}
func delVoteComplete(ip string) {key := "complete_" + ipredis_util.Del(key)
}

1.5、路由配置

package routesimport ("fmt""github.com/gin-gonic/gin""go-vote/controllers"
)var Router *gin.Enginefunc init() {gin.SetMode(gin.ReleaseMode)Router = gin.Default()Router.Static("/static", "./static")Router.StaticFile("/vote.html", "./html/vote.html")Router.POST("/vote", controllers.Vote)Router.GET("/view", controllers.View)
}

1.6、启动程序入口实现

package main
import ("go-vote/routes""go-vote/services"
)
func main() {go services.VoteJob()Router := routes.RouterRouter.Run(":8080")
}

2、压测结果

测试结果是在4核8G的Centos7虚拟机上压测。

2.1、设置Jmeter线程组

线程数1000,Ramp-up为1秒,循环次数1000,共产生100万条投票压测数据。

在这里插入图片描述

2.2、Jmeter聚合报告结果,支持11240/秒吞吐量

在这里插入图片描述

2.3、Jmeter TPS结果,支持15000/秒最大并发

在这里插入图片描述

2.4、总CPU和总内存变化情况

CPU从0%上升到31.2%最大值,随后在这个范围内上下浮动。
内存也在不断上升,压入100万数据后,内存从1.7GB上升到2.3GB,随后下降。
在这里插入图片描述

2.5、Redis和go进程占用资源

go应用./main:CPU从0%上升到280%;内存从0.3%上升到0.8%,变化不大;
redis-server:CPU从0%上升到81.2%;内存从10.3%上升到12.9%;
在这里插入图片描述
测试源码下载

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/678.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【情侣博客网站】

效果图 PC端 建塔教程 第一步:下载网站源码(在文章下方有下载链接) 第二步:上传到服务器或虚拟主机,解压。 第三步:这一步很关键,数据库进行连接,看图 admin/connect.php就是这…

el-menu 该有的页面显示不出来第一个应该想到的问题首先就算检查是否多写了一个 , 导致显示不出来原有的页面

问题描述 el-menu 该有的页面显示不出来第一个应该想到的问题首先就算检查是否多写了一个 , 导致显示不出来原有的页面 如图所示多写了一个,就会导致该有的页面显示不出来。

【nginx代理和tengine的启动-重启等命令】

在nginx成功启动后[任务管理器有nginx.exe进程],运行vue项目,在浏览器访问http://localhost:10001/,提示:访问拒绝(调试中network某些地址403); 解决方案: localhost改为ip&#xff…

【Flutter】GetX状态管理及路由管理用法

目录 一、安装二、使用1.安装GetX插件,快捷生成模版代码2.主入口MaterialApp改成GetMaterialApp3.定义路由常量RoutePath类、别名映射页面RoutePages类4. 初始initialRoute,getPages。5.调用 总结 一、安装 dependencies: get: ^4.6.6二、使用 1.安装G…

MDK-ARM Keil5.38 下载安装环境搭建

一、keil软件介绍 KEIL是公司的名称,有时候也指KEIL公司的所有软件开发工具,目前2005年Keil由ARM公司收购,成为ARM的公司之一。 MDK(Microcontroller Development Kit) 也称MDK-ARM、KEIL MDK、RealView MDK、KEIL For…

[最新]访问/加速StackOverFlow的方法

但是有很多问题都是在StackOverFlow上有现成的解决方案,而某度搜索引擎…前一页的回答互相抄袭,看着实在胀眼睛。 话不多说,解决办法: 直接访问插件商店下载插件(最快捷方便,点点就行)&#x…

Python中的迭代器:深入理解与实用指南

文章目录 1. 迭代器的基本概念2. Python中的迭代器实例3. 自定义迭代器3.1 例子3.2 详细过程 4. 迭代器的高级应用5. 常见问题与解答 迭代器是Python中非常核心的概念之一,在面试中也会被问到。下面我会详细介绍什么是迭代器,使用方法,以及使…

怎么转行做产品经理?

小白转产品经理第一点要先学基础理论知识,学了理论再去实践,转行,跳槽! 学理论比较好的就是去报NPDP的系统班,考后也会有面试指导课、职场晋升课程,对小白来说非常合适了~(B站:不爱…

探索 IntelliJ IDEA 2024.1最新变化:全面升级助力编码效率

探索 IntelliJ IDEA 2024.1最新变化:全面升级助力编码效率 文章目录 探索 IntelliJ IDEA 2024.1最新变化:全面升级助力编码效率摘要引言 IntelliJ IDEA 2024.1 最新变化关键亮点全行代码补全 Ultimate对 Java 22 功能的支持新终端 Beta编辑器中的粘性行 …

『FPGA通信接口』串行通信接口-IIC(2)EEPROM读写控制器

文章目录 1.EEPROM简介2.AT24C04简介3.逻辑框架设计4.随机读写时序5.仿真代码与仿真结果分析6.注意事项7.效果8.传送门 1.EEPROM简介 EEPROM (Electrically Erasable Programmable read only memory) 是指带电可擦可编程只读存储器。是一种掉电后数据不丢失的存储芯片。在嵌入…

uniapp项目中表单输入完整项之后提交按钮颜色高亮显示并触发点击事件

1.效果图&#xff1a; 2.html <view class"add" :style"{background: dynamicBackgroundColor, border-color: white}" click"handleClick">添加新地址 </view> 3.js formData: {name: ,phoneNumber: ,addressDetail: }//利用com…

JVM 性能调优命令(jps,jinfo,jstat,jstack,jmap)

常用命令&#xff1a;jps、jinfo、jstat、jstack、jmap jps jps查看java进程及相关信息 jps -l 输出jar包路径&#xff0c;类全名 jps -m 输出main参数 jps -v 输出JVM参数jps命令示例 显示本机的Java虚拟机进程&#xff1a; # jps 15729 jar 92153 Jps 90267 Jstat显示主类…

嵌入式Linux:Linux系统文件目录说明

在Linux系统中&#xff0c;系统文件和目录按照一定的约定被组织和分配到不同的位置。这些文件和目录通常用于存储系统配置、程序文件、库文件等。 以下是一些常见的系统文件目录及其用途的详细说明&#xff1a; /bin:存放系统中最基本的命令&#xff08;二进制文件&#xff09;…

YOLOv9改进策略 | Neck篇 | 2024.1最新MFDS-DETR的HS-FPN改进特征融合层(轻量化Neck、全网独家首发)

一、本文介绍 本文给大家带来的改进机制是最近这几天最新发布的改进机制MFDS-DETR提出的一种HS-FPN结构&#xff0c;其是一种为白细胞检测设计的网络结构&#xff0c;主要用于解决白细胞数据集中的多尺度挑战。它的基本原理包括两个关键部分&#xff1a;特征选择模块和特征融合…

【单调栈】力扣85.最大矩形

好久没更新了 ~ 我又回来啦&#xff01; 两个好消息&#xff1a; 我考上研了&#xff0c;收到拟录取通知啦&#xff01;开放 留言功能 了&#xff0c;小伙伴对于内容有什么疑问可以在文章底部评论&#xff0c;看到之后会及时回复大家的&#xff01; 前面更新过的算法&#x…

《QT实用小工具·三十二》九宫格炫酷主界面

1、概述 源码放在文章末尾 项目实现了九宫格炫酷主界面&#xff0c;下面是项目demo演示&#xff1a; 项目部分代码如下&#xff1a; #pragma execution_character_set("utf-8")#include "frmmain.h" #include "ui_frmmain.h"frmMain::frmMain…

噪声系数测试之增益法

提到增益法测试噪声系数,大家并不陌生,这是一种简洁的测试方法,精度不如Y因子法,但是在某些测试场合,比如只有频谱仪而没有噪声头时,且待测件具有非常高的增益时,就可以使用增益法测试噪声系数。 增益法测试噪声系数的连接示意图如图1所示,其思路为:DUT输入端端接50 …

jsoup接收429,404错误用来接收json格式

1.代码用例 try { // 拿到当前剩余余下的钱Document doc Jsoup.connect(url).header("Authorization", "Bearer " apiKey).header("Content-Type", "application/json").header("Connection", "keep-aliv…

就业班 第三阶段(nginx) 2401--4.19 day3 nginx3

二、企业 keepalived 高可用项目实战 1、Keepalived VRRP 介绍 keepalived是什么keepalived是集群管理中保证集群高可用的一个服务软件&#xff0c;用来防止单点故障。 ​ keepalived工作原理keepalived是以VRRP协议为实现基础的&#xff0c;VRRP全称Virtual Router Redundan…

VirtualBox Manjaro Linux(kde)虚拟机扩容 增大硬盘存储空间

https://blog.csdn.net/m0_65274357/article/details/131965463 df -h发现/可用空间之后几百M了 文件系统 大小 已用 可用 已用% 挂载点 dev 2.0G 0 2.0G 0% /dev run 2.0G 1.2M 2.0G 1% /run /dev/sda1 40G 37…