Golang 开发SSE流接口,实现ChatGpt逐字打印效果
一直来没时间优化一下我这个gpt,因为最开始弄这个设计成所有数据请求完之后一次性返给前端,这样就需要等gpt处理完所有数据,消耗的时间比较多。所以造成的结果就是请求一次对话需要等待5-15秒不等。实在是影响体验性。这两天稍微有点时间,就琢磨把gpt接口改成一个字一个字或者一段话一段话输出,在网上查找了一些资料,发现有很多处理方式,比如Websocket,SSE之类的,最常见的还是SSE,因为它没有消耗太多资源,对于这样低配置服务器来说是最好的处理方式了。好,接下里我直接上代码,查找资料过程略过。后端:我这里拉Gin服务,其他也行。SSE流接口处理:(流接口主要是三个地方调整)Content-Type":"text/event-stream"Cache-Control":"no-cache"Connection":"keep-alive"package main import ( "time" "github.com/gin-gonic/gin" ) func main() { // 创建路由 router := gin.Default() router.Any("/stream", func(c *gin.Context) { c.Header("Content-Type", "text/event-stream") c.Header("Cache-Control", "no-cache") c.Header("Connection", "keep-alive") c.SSEvent("start", "start") for i := 0; i < 10; i++ { c.Writer.WriteString("data: SSE data\n\n") if i == 9 { c.SSEvent("end", "end") } c.Writer.Flush() time.Sleep(10 * time.Millisecond) } }) // 启动服务 router.Run(":8080") } 前端:(我是在nuxt3框架中处理的,你也可以直接在html页面处理一样的方法,直接copy)<div> <ul> <li> {{ messageStr }} </li> </ul> <div @click="clickHandle">点我</div> </div> </template> <script setup> import { reactive, onMounted, onBeforeUnmount } from "vue"; let messageStr = ref(""); let getData = ()=>{ const sseSource = new window.EventSource('/ccc/stream'); sseSource.addEventListener('message', (event) => { console.log(event.data); messageStr.value = messageStr.value+event.data; }); sseSource.addEventListener('end', (event) => { sseSource.close(); }); } let clickHandle = ()=>{ getData(); }好了,前后端已完成,是不是很简单。现在我们来看下测试效果。
查看详情点赞23评论收藏4浏览13622023-10-20 10:13:27Docker-compose 一键部署 Nginx+PHP+MySql+Redis 环境
经过上面docker分别单独部署,nginx,PHP,mysql,redis服务。今天经过半天终于用docker-compose集成了Nginx+PHP+MySql+Redis环境,一键部署,确实方便太多了。好了,回归正题,下面我部署环境的流程。准备工作:安装 Docker(参考之前文章)安装 Docker-compose (参考之前文章)大家可以用我集成好的docker-compose文件,部署分为三步。1、下载文件 docker-compose 部署包下载地址:https://www.aliyundrive.com/s/AwX2ff4ZLyu 提取码: 2f8xGitee仓库地址 :https://gitee.com/lingfeng9527/docker-compose.git2、解压上传到服务器任务一个位置3、执行命令:docker-compose up -d好了,已经部署好了。访问地址就可以看到结果。HTML:PHP文件访问测试MySQL连接:测试Redis:可以看到redis也是没什么问题了。上面已经验证了结果,接下来我们分别说明集成过程,嫌麻烦的朋友或者熟手可以不用看下面的内容!首先准备工作,准备Nginx+PHP+MySql+Redis环境所需要的文件夹和文件:docker-compose 文件:version: '3' #docker-compose语法的版本 services: php: #创建 php的容器 container_name: php74 image: php:7.4.3-fpm ports: - "9000:9000" restart: always privileged: true links: - "mysql" environment: - TZ=Asia/Shanghai build: ./php #直接到 ./php文件下找Dockerfile volumes: - ./www:/www #把容器的/var/www 映射到./www nginx: #创建 nginx容器 container_name: nginx image: nginx:latest ports: #映射 80和443端口到本机 - 80:80 - 8001:8001 - 4433:443 restart: always depends_on: - "php" links: - "php:php74" environment: - TZ=Asia/Shanghai privileged: true volumes: - ./nginx/nginx.conf:/etc/nginx/nginx.conf #把容器的nginx.conf映射到 ./nginx/nginx.conf - ./nginx/conf.d:/etc/nginx/conf.d #把容器的conf.d映射到 ./nginx/conf.d - ./www:/usr/share/nginx/html #把容器的/usr/share/nginx/html 映射到./www - ./nginx/logs:/var/log/nginx #把容器的/var/log/nginx 映射到./nginx/logs mysql: container_name: mysql image: mysql:latest volumes: - ./mysql/data:/var/lib/mysql #把容器的数据文件夹映射到 ./mysql/data - ./mysql/logs:/var/log/mysql #把容器的日志文件夹映射到 ./mysql/log - ./mysql/conf:/etc/mysql/conf.d #把容器的my.conf文件映射到 ./mysql/my.conf 暂时去掉 ports: - 3306:3306 #把容器的3306端口映射到本机的3306 restart: always privileged: true environment: - TZ=Asia/Shanghai - MYSQL_ROOT_PASSWORD=469ge9g449646he #root密码(我这里乱输的,自己可改) - MYSQL_USER=test #另一个帐号 - MYSQL_PASS=123456 #另一个帐号的密码 redis: #创建 reids的容器 container_name: redis image: redis:latest restart: always ports: - "6379:6379" environment: - TZ=Asia/Shanghai volumes: - ./redis/conf/redis.conf:/etc/redis/redis.conf #把容器的配置文件夹映射到 ./redis/config/redis.conf - ./redis/data:/data command: /bin/sh -c "echo 'vm.overcommit_memory = 1' >> /etc/sysctl.conf && redis-server /etc/redis/redis.conf --appendonly yes" # 指定配置文件并开启持久化 privileged: true # 使用该参数,container内的root拥有真正的root权限。否则,container内的root只是外部的一个普通用户权限docker-compose 文件就是用来管理各个docker容器服务。www:这个主要是放所有的网站项目,html文件或者PHP文件,如果需要放其他语言的文件,可以参考我的方法集成其他环境。Nginx:Nginx所有相关的配置文件、日志文件MySQL:相关的配置文件,数据文件,日志文件PHP:Dockerfile文件,主要是安装PHP的相关东西,包括PHP扩展。Redis:redis配置和数据文件mkdir /docker-compose/mysql mkdir /docker-compose/mysql/conf touch /docker-compose/mysql/conf/my.conf mkdir /docker-compose/mysql/data mkdir /docker-compose/mysql/logs mkdir /docker-compose/nginx mkdir /docker-compose/nginx/conf.d mkdir /docker-compose/nginx/conf.d/www.test.com.conf mkdir /docker-compose/nginx/conf.d/www.testphp.com.conf mkdir /docker-compose/nginx/logs touch /docker-compose/nginx/nginx.conf mkdir /docker-compose/php touch /docker-compose/php/Dockerfile mkdir /docker-compose/redis mkdir /docker-compose/redis/conf touch /docker-compose/redis/conf/redis.conf mkdir /docker-compose/redis/data mkdir /docker-compose/www touch /docker-compose/www/index.html #可以放入一些html文件 touch /docker-compose/www/php_dome/index.php #可以放入一些php文件 touch docker-compose.ymlmy.conf 文件内容:[mysqld] max_allowed_packet = 20M innodb_buffer_pool_size=128M query_cache_size=56M key_buffer_size = 16M table_open_cache = 64 sort_buffer_size = 512K net_buffer_length = 8K read_buffer_size = 256K read_rnd_buffer_size = 512K myisam_sort_buffer_size = 8M log-bin=/var/lib/mysql/binlog server-id=344 binlog_format=ROWwww.test.com.conf 文件内容:server { listen 80; server_name 192.168.11.146; location / { #注意这里的root目录是容器的网站目录 root /usr/share/nginx/html/; index index.html index.htm index.php; } }www.testphp.com.conf 文件内容:server { listen 8001; server_name 192.168.11.146; location / { root /usr/share/nginx/html/php_demo; index index.html index.htm index.php; } error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/share/nginx/html; } location ~ \.php$ { fastcgi_pass php:9000; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME /www/$fastcgi_script_name; include fastcgi_params; } }nginx nginx.conf 文件内容:user nginx; worker_processes 1; error_log /var/log/nginx/error.log warn; pid /var/run/nginx.pid; events { worker_connections 1024; } http { include /etc/nginx/mime.types; default_type application/octet-stream; log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; access_log /var/log/nginx/access.log main; sendfile on; #tcp_nopush on; keepalive_timeout 65; client_max_body_size 66M; gzip on; include /etc/nginx/conf.d/*.conf; }PHP Dockerfile 文件内容:FROM php:7.4.3-fpm # packages RUN apk --update add \ autoconf \ build-base \ linux-headers \ libaio-dev \ zlib-dev \ curl \ git \ subversion \ freetype-dev \ libjpeg-turbo-dev \ libmcrypt-dev \ libpng-dev \ libtool \ libbz2 \ bzip2 \ bzip2-dev \ libstdc++ \ libxslt-dev \ openldap-dev \ imagemagick-dev \ make \ unzip \ wget && \ docker-php-ext-install bcmath mcrypt zip bz2 pdo_mysql mysqli simplexml opcache sockets mbstring pcntl xsl && \ docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ && \ pecl install imagick && \ docker-php-ext-enable imagick && \ pecl install swoole && \ docker-php-ext-enable swoole && \ docker-php-ext-install gd && \ docker-php-ext-enable opcache && \ apk del build-base \ linux-headers \ libaio-dev \ && rm -rf /var/cache/apk/* ENV COMPOSER_ALLOW_SUPERUSER 1 ENV COMPOSER_HOME /tmp ENV COMPOSER_VERSION 1.5.1 RUN curl -s -f -L -o /tmp/installer.php https://raw.githubusercontent.com/composer/getcomposer.org/da290238de6d63faace0343efbdd5aa9354332c5/web/installer \ && php -r " \ \$signature = '669656bab3166a7aff8a7506b8cb2d1c292f042046c5a994c43155c0be6190fa0355160742ab2e1c88d40d5be660b410'; \ \$hash = hash('SHA384', file_get_contents('/tmp/installer.php')); \ if (!hash_equals(\$signature, \$hash)) { \ unlink('/tmp/installer.php'); \ echo 'Integrity check failed, installer is either corrupt or worse.' . PHP_EOL; \ exit(1); \ }" \ && php /tmp/installer.php --no-ansi --install-dir=/usr/bin --filename=composer --version=${COMPOSER_VERSION} \ && rm /tmp/installer.php \ && composer --ansi --version --no-interaction VOLUME /var/www WORKDIR /var/www CMD php-fpm好了,到这里基本上都介绍完了。
查看详情点赞21评论收藏1浏览5882023-05-19 15:35:40Vite3+Vue3+TypeScript+Pinia+Element-plus 后台管理系统前端模板分享
前端技术栈:Vite3+Vue3+TypeScript+Pinia+Element-plus后台管理系统模板-阿里云盘地址:https://www.aliyundrive.com/s/B7VuYPpR1yT提取码: h42hGit地址:https://gitee.com/lingfeng9527/web_admin.git页面截图:(登录,用户管理,权限管理,菜单管理,文章分类,文章列表)等vite3Vite(法语意为 "快速的",发音 /vit/,发音同 "veet")是一种新型前端构建工具,能够显著提升前端开发体验。它主要由两部分组成:一个开发服务器,它基于 原生 ES 模块 提供了 丰富的内建功能,如速度快到惊人的 模块热更新(HMR)。一套构建指令,它使用 Rollup 打包你的代码,并且它是预配置的,可输出用于生产环境的高度优化过的静态资源。Vite 意在提供开箱即用的配置,同时它的 插件 API 和 JavaScript API 带来了高度的可扩展性,并有完整的类型支持。你可以在 为什么选 Vite 中了解更多关于项目的设计初衷。vue3vue3.0 向下兼容 vue2.x 版本,优化了主要核心双向绑定原理和体积大小,并且更加友好的兼容 ts 语法。打包大小减少初次渲染快 , 更新渲染快内存减少TypeScriptTypeScript是微软开发的一个开源的编程语言,通过在JavaScript的基础上添加静态类型定义构建而成。TypeScript通过TypeScript编译器或Babel转译为JavaScript代码,可运行在任何浏览器,任何操作系统。PiniaPinia 是 Vue 的存储库,它允许您跨组件/页面共享状态。 如果您熟悉 Composition API,您可能会认为您已经可以通过一个简单的 export const state = reactive({}). 这对于单页应用程序来说是正确的,但如果它是服务器端呈现的,会使您的应用程序暴露于安全漏洞。 但即使在小型单页应用程序中,您也可以从使用 Pinia 中获得很多好处:dev-tools 支持热模块更换插件:使用插件扩展 Pinia 功能为 JS 用户提供适当的 TypeScript 支持或 autocompletion服务器端渲染支持
查看详情点赞18评论2收藏1浏览8942023-03-01 11:21:28Git 常用命令
使用git pull或者git push每次都需要输入用户名和密码很繁琐,耽误时间,现在教大家一条命令实现保存用户名和密码不用再输入git config --global credential.helper store git pull /git push (第一次输入,后续就不用再次数据)拉项目:git clone +地址$ git clone http://172.16.0.25/SEM_CENTER/192.168.254.119.git git clone -b dev进入项目,跟cmd一样cd /dir切换分支git checkout dev上传修改的代码git add . git commit -m "备注" git push //或者 git push origin devGit 命令 & dev 分支合并到 master1. 提到devgit add -A git commit -m ‘dev' git pull git push2. 切换到本地mastergit checkout master3. 同步远程 master 代码git pull origin master //或者 git pull4. 然后将dev合并到mastergit merge dev5. 将合并完的分支push到远程仓库git push origin master // 或者 git push查看本地分支git branch查看远程分支git branch -r查看所有分支git branch -a新建本地分支git branch xxx删除本地分支git branch -d xxx讲本地创建的分支推到远程git push origin xxx删除远程的分支//删除分支 git branch -r -d origin/lxl 提交并将远程分支删除 git push origin :lxl //这个也行 git push origin --delete new_a 将本地dev分支合并到本地master分支$ git merge dev #执行合并代码,此时执行结果时将本地的dev合并到本地master分支回退//回退到上一个版本 git reset --hard HEAD^ //回退到3次提交之前 git reset --hard HEAD~3 //置顶回退到某个版本代码 git reset --hard dde8c25694f34acf8971f0782b1a676f39bf0a46 //强推到远程 git push origin HEAD --force
查看详情点赞15评论收藏浏览862023-02-27 18:09:07分布式事务控制
分布式事务是指在分布式系统中跨多个节点和多个数据库执行的事务。由于分布式事务存在网络延迟、节点故障、数据同步等问题,因此需要采用一些控制方法来保证事务的正确性和一致性。以下是一些常用的分布式事务控制方法:两阶段提交(2PC):2PC 是一种经典的分布式事务协议,通过协调器(Coordinator)和参与者(Participant)两个角色来实现分布式事务的提交和回滚。2PC 分为投票阶段和提交阶段两个阶段,其中在投票阶段,协调器向所有参与者发送提交请求,参与者返回同意或者拒绝,如果所有参与者都同意,则进入提交阶段,否则回滚事务。2PC 算法能够保证事务的一致性,但是会带来性能和可扩展性的问题。三阶段提交(3PC):3PC 是在 2PC 的基础上发展而来,通过增加准备阶段来避免了 2PC 中的两阶段问题。3PC 分为准备阶段、提交阶段和确认阶段,其中在准备阶段,协调器会向参与者发送准备请求,参与者返回准备就绪或者失败,如果所有参与者都准备就绪,则进入提交阶段,否则回滚事务。在提交阶段,协调器向所有参与者发送提交请求,参与者返回提交就绪或者失败,如果所有参与者都提交就绪,则进入确认阶段,否则回滚事务。3PC 算法相对于 2PC 可以减少一次网络通信,但是仍然存在性能和可扩展性问题。补偿事务(Compensating Transaction):补偿事务是一种更加灵活的分布式事务处理方法,它通过记录操作的逆向操作,来实现事务的回滚和恢复。当发生异常时,通过执行补偿操作来回滚事务,从而保证数据的一致性。补偿事务相对于 2PC 和 3PC 更加灵活,但是需要开发者手动实现补偿逻辑,并且可能会引入一些数据不一致性的问题。消息队列(Message Queue):消息队列是一种异步处理和解耦的分布式消息传递机制,可以用于实现分布式事务的处理。通过将需要提交的事务操作封装成消息,将消息发送到消息队列中,然后由消费者节点异步处理消息,最终完成事务操作。消息队列相对于传统的分布式事务控制方法的优点是可以解耦和提高系统的可扩展性和可靠性。但是也存在一些缺点,比如性能、可靠性、一致性和复杂性等问题。因此,在实际应用中需要根据具体的场景和需求来选择合适的分布式事务控制方法。另外,除了以上提到的控制方法,还有一些其他的分布式事务控制方法,比如 TCC(Try-Confirm-Cancel)事务、本地消息表(Local Message Table)等。这些方法都有各自的优缺点和适用场景,需要根据实际情况进行选择。总的来说,分布式事务的控制是一个比较复杂和关键的问题,需要考虑各种因素,包括性能、可靠性、一致性和复杂性等。在实际应用中需要根据具体的场景和需求来选择合适的控制方法,并且需要注意分布式事务的设计和实现,以保证事务的正确性和一致性。
查看详情点赞13评论收藏浏览512023-03-02 11:43:09沉浸式翻译 --一键开启双语阅读,提升信息获取效率
随着谷歌翻译退出中国市场,市场上迫切需要一款能够替代谷歌翻译的工具。在这个背景下,沉浸式翻译应运而生,成为一款备受关注的翻译工具。 沉浸式翻译是一款浏览器插件,官方网址为: https://immersivetranslate.com/ ,它可以智能识别网页主内容区进行双语翻译,插件支持全平台浏览器,PDF文件翻译,EPUB电子书双语翻译、制作、导出,字幕文件翻译等功能。 沉浸式翻译是一款全面支持80多种语言翻译的工具。以下是该工具的主要特点和功能解释: 1. 多语言支持:沉浸式翻译可以翻译80多种语言,让您轻松应对各种语言交流需求。 2. 自定义翻译服务:您可以自由选择多种翻译服务,以满足个性化的翻译偏好。 3. 翻译设置定制:您可以自定义哪些语种需要翻译,哪些语种不需要翻译,甚至可以设定特定网址的翻译与否。 4. 双语对照显示:沉浸式翻译的界面同时显示原语言和翻译后的语言,方便您进行一一对照,确保准确性。 5. 多样化译文样式:您可以根据个人喜好设置多种译文样式,包括字体、颜色等,以使翻译结果更加符合您的偏好。 6. 配置导入导出:支持导入和导出配置,方便您在不同设备间进行设置同步或备份。 7. 免费使用:最重要的是,沉浸式翻译是免费使用的,为您提供便捷的翻译服务,无需支付任何费用。 推荐使用!
查看详情点赞13评论1收藏1浏览2292023-06-30 09:59:22Go 实现WebSocket,初步实操
什么是长连接和短连接?在HTTP/1.0中默认使用短连接。也就是说,客户端和服务器每进行一次HTTP操作,就建立一次连接,任务结束就中断连接。当客户端浏览器访问的某个HTML或其他类型的Web页中包含有其他的Web资源(如JavaScript文件、图像文件、CSS文件等),每遇到这样一个Web资源,浏览器就会重新建立一个HTTP会话。而从HTTP/1.1起,默认使用长连接,用以保持连接特性。使用长连接的HTTP协议,会在响应头加入这行代码:Connection:keep-alive在使用长连接的情况下,当一个网页打开完成后,客户端和服务器之间用于传输HTTP数据的TCP连接不会关闭,客户端再次访问这个服务器时,会继续使用这一条已经建立的连接。Keep-Alive不会永久保持连接,它有一个保持时间,可以在不同的服务器软件(如Apache)中设定这个时间。实现长连接需要客户端和服务端都支持长连接。HTTP协议的长连接和短连接,实质上是TCP协议的长连接和短连接。Go WebSocket今天我们会用Go语言使用WebSocket,经过多方收集资料,整理了后端的服务端+客户端,然后服务端和前端的链接两个示例,下面是代码,供大家研究。今天我们用的是Gin框架实现一个长链接。1、服务端+客户端(后端)服务端:package main import ( "fmt" "log" "net/http" "time" "github.com/gin-gonic/gin" "github.com/gorilla/websocket" ) var upgrader = websocket.Upgrader{} func main() { // 使用gin框架,和普通的http协议的服务器没什么不一样 s := gin.Default() s.GET("/sendMsg", echo) _ = s.Run("127.0.0.1:8090") } func echo(c *gin.Context) { //服务升级,对于来到的http连接进行服务升级,升级到ws cn, err := upgrader.Upgrade(c.Writer, c.Request, nil) defer cn.Close() if err != nil { panic(err) } for { mt, message, err := cn.ReadMessage() fmt.Println("接受到消息", message) if err != nil { log.Println("server read:", err) break } log.Printf("server recv msg: %s", message) msg := string(message) fmt.Println(msg, "msg") if msg == "clent" { message = []byte("客户端来了") } err = cn.WriteMessage(mt, message) if err != nil { log.Println(" server write err:", err) break } } } 客户端:package main import ( "log" "net/url" "os" "os/signal" "time" "github.com/gorilla/websocket" ) func main() { interrupt := make(chan os.Signal, 1) signal.Notify(interrupt, os.Interrupt) u := url.URL{Scheme: "ws", Host: "127.0.0.1:8090", Path: "/sendMsg"} log.Printf("client1 connecting to %s", u.String()) c, _, err := websocket.DefaultDialer.Dial(u.String(), nil) if err != nil { log.Fatal("dial server:", err) } defer c.Close() done := make(chan struct{}) go func() { defer close(done) for { _, message, err := c.ReadMessage() if err != nil { log.Println("client read err:", err) return } log.Printf("client recv msg: %s", message) } }() for { select { // if the goroutine is done , all are out case <-done: return case <-time.Tick(time.Second * 5): err := c.WriteMessage(websocket.TextMessage, []byte("clent")) if err != nil { log.Println("client write:", err) return } case <-interrupt: log.Println("client interrupt") err := c.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")) if err != nil { log.Println("client1 write close:", err) return } select { case <-done: case <-time.After(time.Second): } return } } } 分别启动服务端和客户端:服务端会根据客户端发来的消息回复2、服务端+前端(前后端)接下来,我们用前后端交互一下websocket前端页面:<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>websocket测试</title> <script src="http://www.alingfeng.cn/js/jquery.min.js"></script> </head> <body> <button id="clickButton">点击测试websocket</button> <script> $("#clickButton").click(function () { var name = $("#name").val(); initWebpack(name); }); </script> <script> var planWebsocket = null; var planIP = "127.0.0.1"; // IP地址 var planPort = "8090"; // 端口号 function initWebpack(clickName) { //初始化websocket if ("WebSocket" in window) { planWebsocket = new WebSocket( "ws://" + planIP + ":" + planPort + "/sendMsg" ); // 通信地址 setInterval( (planWebsocket.onopen = function (event) { console.log("建立连接"); let sendData = { command: "sendMsg", data: [{ msg: "服务端,我发送了消息,注意查收!" }], }; planWebsocket.send(JSON.stringify(sendData)); // 发送获取数据的接口 }), 2000 ); planWebsocket.onmessage = function (event) { // console.log('收到消息:' + event.data) let data = JSON.parse(event.data); if (data.command == "sendMsg") { var planData = data.data; //返回的数据 console.log(planData); } else if (data.command == "getscenes") { // 其他命令 } }; planWebsocket.onclose = function (event) { console.log("连接关闭"); }; planWebsocket.onerror = function () { alert("websocket通信发生错误!"); }; } else { alert("该浏览器不支持websocket!"); } } // initWebpack(); //调用 </script> </body> </html> 服务:package main import ( "fmt" "log" "net/http" "time" "github.com/gin-gonic/gin" "github.com/gorilla/websocket" ) // var upgrader = websocket.Upgrader{} var upgrader = websocket.Upgrader{ ReadBufferSize: 1024, WriteBufferSize: 1024, // 解决跨域问题 CheckOrigin: func(r *http.Request) bool { return true }, } func main() { // 使用gin框架,和普通的http协议的服务器没什么不一样 s := gin.Default() s.GET("/sendMsg", echo) _ = s.Run("127.0.0.1:8090") } func echo(c *gin.Context) { //服务升级,对于来到的http连接进行服务升级,升级到ws cn, err := upgrader.Upgrade(c.Writer, c.Request, nil) defer cn.Close() if err != nil { panic(err) } for { mt, message, err := cn.ReadMessage() fmt.Println("接受到消息", message) if err != nil { log.Println("server read:", err) break } log.Printf("server recv msg: %s", message) msg := string(message) fmt.Println(msg, "msg") // if msg == "clent1" { // message = []byte("客户端1来了") // } else if msg == "clent2" { // message = []byte("你好客户端2") // } message = []byte("客户端,我已接收") for i := 0; i < 100; i++ { time.Sleep(1000000000) err = cn.WriteMessage(mt, message) } if err != nil { log.Println(" server write err:", err) break } } } 测试:我们初步了解Go语言做websocket就可以了。
查看详情点赞8评论收藏1浏览1272023-06-03 10:04:00Go 接入RabbitMq实操
基本概念什么是消息队列消息队列是一种应用(进程)间的通信方式。生产者只需把消息发布到MQ,消费者只需重MQ中取出,可靠传递由消息队列中的消息系统来确保。消息队列有什么用消息队列是一种异步协作机制,最根本的用处在于将一些不需要即时生效的操作拆分出来异步执行,从而达到可靠传递、流量削峰等目的。比如如果有一个业务需要发送短信,可以在主流程完成之后发送消息到MQ后,让主流程完结。而由另外的线程拉取MQ的消息,完成发送短信的操作。常用的消息队列常用的MQ大概有ActiveMQ、RabbitMQ、RocketMQ、KafkaActiveMQ,基于Java优点:对Java的JMS支持最好;多线程并发;缺点:历史悠久,版本更新慢。现在慢慢用的少了;RabbitMQ,基于Erlang优点:生态丰富,是现在主流的MQ;支持多种客户端、支持AJAX;缺点:对想深入源码的Java选手不太友好;RocketMQ,基于Java优点:为海量数据打造;主张拉模式;天然集群、HA、负载均衡;缺点:生态较小Kafka,基于Scala优点:分布式高可拓展;高性能;容错强缺点:消息重复;乱序;维护成本高什么是RabbitMQ消息中间件erlang:一种并发函数式语言AMQP:Advanced Message Queuing Protocol,高级消息队列协议。由Exchange、Queue和Bind组成RabbitMQ是一个erlang开发的AMQP实现生产者将消息发送到Exchange上,通过Exchange从而Binding到Queues上。Exchange有三种具体类型:direct:如果消息中的RoutingKey和Binding中的BindingKey一致就转发fanout:消息被分发到所有队列中topic:将RoutingKey和队列的模式进行匹配应用场景异步可以理解为将遇到非必须的业务时,立即响应客户端,不关系业务何时完成比如在用户注册时,有将信息写入数据库和发送注册成功邮件两项业务。数据库写入完成即标志着用户注册成功,此时如果继续处理发送邮件的业务,会给客户端带来不必要的等待时间。引入消息队列后,在队列中写入完成注册的消息后,即可完成整个注册流程。至于邮件,可以等到邮件业务从消息队列中取出消息再发送。把不紧急的业务从主线中剥离出来,主线不必考虑不紧急的业务何时完成的时候,可以考虑使用消息队列实现异步。解耦考虑两个系统间存在消息传递,一个系统的故障会影响到整个业务的正常运转。可以用消息队列来保证消息可靠传递比如一个订单系统和一个库存系统,完成订单之后,需要进行库存调度。考虑到如果库存系统故障,会引起已完成的订单消息的丢失,而做很多异常处理会使业务变得臃肿。这个时候可考虑引入消息队列,使用消息队列保证可靠传输,从而减少业务逻辑。削峰考虑短时间的大量请求,可能会带来内存溢出、大面积连接超时等情况,使得服务器崩溃。引入消息队列后,可以控制请求到业务处理系统的流量,从而防止崩溃现象的出现。比如秒杀场景。大量请求同时涌入,服务器不能分配足够的资源响应,或者带宽不足,导致宕机。可以引入消息队列来限流,MQ通过限制同一时间的出口消息,使得流量在服务器能够承受的范围之内。等待一部分请求处理完成之后,再向业务处理系统导入新的消息。----------------------------------------------------------------------------------------------------------------------------------------昨天我们用docker在虚拟机上装了RabbitMq,今天我们就开始用它来实际操作一下。不说废话了,我们开始搞,我这里用的是Go语言。首先,我们先封装方法:package rabbitmq import ( "fmt" "github.com/streadway/amqp" ) type RabbitMq struct { Conn *amqp.Connection Ch *amqp.Channel QueueName string // 队列名称 ExchangeName string // 交换机名称 ExchangeType string // 交换机类型 RoutingKey string // routingKey } type QueueAndExchange struct { QueueName string // 队列名称 ExchangeName string // 交换机名称 ExchangeType string // 交换机类型 RoutingKey string // routingKey } func (r *RabbitMq) ConnMq() { conn, err := amqp.Dial("amqp://admin:123456@192.168.11.66:5672/my_vhost") if err != nil { fmt.Printf("连接mq出错,错误信息为:%v\n", err) return } r.Conn = conn } func (r *RabbitMq) CloseMq() { err := r.Conn.Close() if err != nil { fmt.Printf("关闭连接出错,错误信息为:%v\n", err) return } } // 开启channel通道 func (r *RabbitMq) OpenChan() { ch, err := r.Conn.Channel() if err != nil { fmt.Printf("开启channel通道出错,错误信息为:%v\n", err) return } r.Ch = ch } // 关闭channnel通道 func (r *RabbitMq) CloseChan() { err := r.Ch.Close() if err != nil { fmt.Printf("关闭channel通道出错,错误信息为:%v\n", err) } } // 生产者 func (r *RabbitMq) PublishMsg(body string) { ch := r.Ch // 创建队列 ch.QueueDeclare(r.QueueName, true, false, false, false, nil) // 创建交换机 ch.ExchangeDeclare(r.ExchangeName, r.ExchangeType, true, false, false, false, nil) // 队列绑定交换机 ch.QueueBind(r.QueueName, r.RoutingKey, r.ExchangeName, false, nil) // 生产任务 ch.Publish(r.ExchangeName, r.RoutingKey, false, false, amqp.Publishing{ ContentType: "text/plain", Body: []byte(body), DeliveryMode: amqp.Persistent, }) } // 创建实例 func NewRabbitMq(qe QueueAndExchange) RabbitMq { return RabbitMq{ QueueName: qe.QueueName, ExchangeName: qe.ExchangeName, ExchangeType: qe.ExchangeType, RoutingKey: qe.RoutingKey, } } 接下来我们就我们创建的生产者发送消息:package main import ( "test_rabbitmq/rabbitmq" "test_rabbitmq/utils" ) func main() { qe := rabbitmq.QueueAndExchange{ QueueName: "test_queue", ExchangeName: "test_exchange", ExchangeType: "direct", RoutingKey: "test_routingKey", } mq := rabbitmq.NewRabbitMq(qe) mq.ConnMq() mq.OpenChan() defer func() { mq.CloseMq() }() defer func() { mq.CloseChan() }() test_map := map[string]interface{}{ "mail": "9527@qq.com", "msg": "今天大太阳", } //这里我们发送100条消息 for i := 0; i < 100; i++ { mq.PublishMsg(utils.MapToStr(test_map)) } } 我们可以看到,发送的消息已经在队列当中了。然后我们开始消费消息:package main import ( "fmt" "net/http" "github.com/gin-gonic/gin" "github.com/streadway/amqp" ) func main() { router := gin.Default() router.GET("/", func(c *gin.Context) { c.String(http.StatusOK, "Hello World") }) router.Run(":8081") } func init() { conn, err := amqp.Dial("amqp://admin:123456@192.168.11.66:5672/my_vhost") fmt.Println(err) defer conn.Close() ch, err_ch := conn.Channel() fmt.Println(err_ch) defer ch.Close() ch.Qos(1, 0, false) deliveries, err := ch.Consume("test_queue", "consumer", false, false, false, false, nil) if err != nil { fmt.Println(err) } //消费成功delivery.Ack(true) for delivery := range deliveries { delivery.Ack(true) body := string(delivery.Body) fmt.Println(body) fmt.Printf("%T\n", body) } } 然后我们可以看到数据已经取出来了,队列的消息也已经消费了。
查看详情点赞5评论收藏2浏览822023-05-24 10:27:24关于目前行业经济
点赞4评论1收藏浏览1562023-05-15 16:25:26个人博客又没多少人看,为什么还要坚持写?
在网上看了很多,都说自己搞的博客没人看,没人访问。10个博客9个夭折。首先说下为什么坚持写博客吧,对于我个人来说。写博客主要是是因为两点:第一点喜欢技术,第二点喜欢分享吧。最近想弄一个博客是因为学了Go语言,所以想自己搭个博客玩玩,不求多高的流量。自己发发文章,分享分享自己的东西。个人略懂一点SEO,算是一个皮毛,所以弄了博客以后,想着做做SEO,但是要到处找资料,这也算是一种成长吧。毕竟接触以前不关注的东西。我的博客技术栈:后端:go-zero + redis + mysql + sqlx,目前用的这些,后期可能尝试加一下消息队列(比如RabbitMq之类)前端:博客用的是nuxt3+Elment-plus,后台用的是vite3+vue3+ts+pinia+Elment-plus希望有机会一起交流!知乎链接:https://zhuanlan.zhihu.com/p/618294882
查看详情点赞3评论1收藏浏览1082023-03-30 21:32:54