使用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://xiahunao.cn/news/2960328.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

用于半监督的图扩散网络 笔记

1 Title Graph Neural Diffusion Networks for Semi-supervised Learning(Wei Ye, Zexi Huang, Yunqi Hong, and Ambuj Singh)【2022】 2 Conclusion This paper proposes a new graph neural network called GND-Nets (for Graph Neural Diffu…

【日常记录】【CSS】利用动画延迟实现复杂动画

文章目录 1、介绍2、原理3、代码4、参考链接 1、介绍 对于这个效果而言,最先想到的就是 监听滑块的input事件来做一些操作 ,但是会发现,对于某一个节点的时候,这个样式操作起来比较麻烦 只看这个代码的话,发现他用的是动画&#x…

异地组网如何安装?

【天联】是一款强大的异地组网安装工具,可以帮助企业实现远程设备的统一管理和协同办公。以下是【天联】可以应用的一些场景: 零售、收银软件应用统一管理:【天联】可以结合医药、餐饮、商超等零售业的收银软件,实现异地统一管理。…

嵌入式4-16

tftpd #include <myhead.h> #define SER_IP "192.168.125.243" //服务器IP地址 #define SER_PORT 69 //服务器端口号 #define CLI_IP "192.168.125.244" //客户端IP地址 #define CLI_PORT 8889 //客户端端…

C# 超高速高性能写日志

1、需求 需求很简单,就是在C#开发中高速写日志。比如在高并发,高流量的地方需要写日志。我们知道程序在操作磁盘时是比较耗时的,所以我们把日志写到磁盘上会有一定的时间耗在上面,这些并不是我们想看到的。 2、解决方案 2.1、简单原理说明 使用列队先缓存到内存,然后我…

初步学习node.js文件模块

环境已安装好&#xff1b; 写一个read1.js如下&#xff1b; var fs require("fs"); var data ;// 创建一个流 var stream1 fs.createReadStream(test1.jsp); stream1.setEncoding(UTF8);// 绑定data事件 stream1.on(data, function(mydata) {data mydata; });/…

openGauss学习笔记-264 openGauss性能调优-TPCC性能调优测试指导-BIOS配置

文章目录 openGauss学习笔记-264 openGauss性能调优-TPCC性能调优测试指导-BIOS配置264.1 恢复BIOS出厂设置264.2 修改相关BIOS设置264.3 重启操作系统 openGauss学习笔记-264 openGauss性能调优-TPCC性能调优测试指导-BIOS配置 本章节主要介绍openGauss数据库内核基于鲲鹏服务…

25 vs code配置

1.中文语言 搜索chinese&#xff0c;安装&#xff0c;等待重新打开 2.remote ssh 安装后F1打开&#xff0c;输入adduser 输入ssh [用户名][主机ip]&#xff0c;添加主机&#xff0c;然后选择保存配置文件 如果出现管道不存在&#xff0c;设置一下 如果出问题&#xff0c;也…

IAR 使用笔记(IAR BIN大小为0异常解决)

烧写 由于芯片的内部SPI FLASH的0级BOOT 程序起到到开启JTAG SW 仿真功能&#xff0c;一旦内部SPI FLASH存储的BL0启动代码被损坏&#xff0c;芯片的JTAG 将不能被连接。所以对BL0的烧写需要谨慎&#xff0c;烧写BL0过程保证芯片不断电。 如果烧写了多备份的启动代码&#xff…

WebRTC直播间搭建记录

考虑到后续增加平台直播的可能性&#xff0c;笔记记录一下WebRTC相关. 让我们分别分析两种情况下的WebRTC连接建立过程&#xff1a; 情况一&#xff1a;AB之间可以直接通信 1.信令交换&#xff1a; 设备A和设备B首先通过信令服务器交换SDP&#xff08;Session Description Pr…

【数据结构(七)】二叉树

❣博主主页: 33的博客❣ ▶文章专栏分类: Java从入门到精通◀ &#x1f69a;我的代码仓库: 33的代码仓库&#x1f69a; &#x1faf5;&#x1faf5;&#x1faf5;关注我带你学更多数据结构的知识 目录 1.前言2.树形结构2.1树的概念2.2常见概念2.3树的表示形式 3.二叉树3.1概念3…

嵌入式学习55-ARM4(ADC和I²C)

1、什么是ADC,模拟量和数字量有什么特点&#xff1f; ADC&#xff1a; …

每日两题 / 15. 三数之和 73. 矩阵置零(LeetCode热题100)

15. 三数之和 - 力扣&#xff08;LeetCode&#xff09; 先确定一个数t&#xff0c;对于剩下的两个数&#xff0c;要求两数之和为t的负数 三数之和就退化成了两数之和&#xff0c;两数之和可以用双指针 先排序&#xff0c;左右两个指针&#xff0c;指向的数之和大于目标值&…

鸿蒙入门02-首次安装和配置

注&#xff1a;还没有安装编辑器&#xff08; deveco studio &#xff09;的小伙伴请看鸿蒙入门01-下载和安装-CSDN博客 首次安装配置 编辑器&#xff08; deveco studio &#xff09;安装完毕以后需要进入配置界面进行相关配置配置完毕以后才可以正常使用 环境配置&#xf…

Unity 左右折叠显示与隐藏UI的简单实现

要实现一个简单的UI左右折叠显示与隐藏&#xff0c;可以结合遮罩&#xff0c;通过代码控制UI区块的宽度和位移来实现。 具体可以按以下步骤实现&#xff1a; 1、新建一个Image组件&#xff0c;并添加精灵&#xff0c;调整大小后&#xff0c;复制一份作为该UI的父物体&#xf…

顺序表(快速上手数据结构)

在介绍ArrayList之前, 我们需要先了解List. List是一个接口,它继承于Collection接口(Collection又继承于最顶层的接口Iterable). 从数据结构的角度来看,List就是一个线性表(Linear List),即n个具有相同类型元素的有限序列, 在该序列上可以执行增删查改等操作. 注意: List是一…

Golang面试题四(GMP)

目录 1.Goroutine 定义 2.GMP 指的是什么 3.GMP模型的简介 全局队列&#xff08;Global Queue&#xff09; P的本地队列 P列表 M列表 4.有关P和M的个数问题 P的数量问题 M的数量问题 P和M何时会被创建 5.调度器P的设计策略 复⽤线程 work stealing机制 hand off…

【linux】mobaterm如何kill pycharm进程

【linux】mobaterm如何kill pycharm进程 【先赞后看养成习惯】求点赞关注收藏&#x1f600; 使用云服务器时&#xff0c;pycharm在打开状态下&#xff0c;不小心关了mobaxterm&#xff0c;然后再输入pycharm.sh就会打不开pycharm&#xff0c;显示已经打开报错&#xff1a;Com…

PyQt程序:实现新版本的自动更新检测及下载(FTP服务器实现)

一、实现逻辑 本实例采用相对简单的逻辑实现,用户在客户端使用软件时点击“检测升级”按钮,连接至FTP服务器检索是否有新版本的.exe,如果有,下载最新的.exe安装升级。 本实例服务端待下载.exe所在目录结构 本实例客户端待更新.exe所在目录结构 二、搭建服务器 可以参考…

openkylin系统通过网线连接ubuntukylin系统上网攻略

openkylin系统通过网线连接ubuntukylin系统上网攻略 主机1&#xff1a;x64 amd &#xff0c;系统&#xff1a;ubuntukylin 22.04 &#xff0c;状态&#xff1a;通过wifi连接热点进行上网&#xff0c;并共享网络。 主机2&#xff1a;x64 intel &#xff0c;系统&#xff1a;ope…