godis源码分析——TCP服务

前言

Godis 是一个用 Go 语言实现的 Redis 服务器。

地址:https://github.com/HDT3213/godis?tab=readme-ov-file

简单架构描述

godis是一个中心服务,是TCP服务。流程大概是:godis开启服务,客户端通过TCP建立连接。客户端发起命令,如’keys k’查询k是否存在,服务端接收到这个命令,从而做出业务逻辑返回结果给客户端。

所以第一步就来看看godis的TCP服务的源码实现

源码目录结构

.
├── LICENSE
├── README.md
├── README_CN.md
├── aof
├── appendonly.aof
├── build-all.sh
├── build-darwin.sh
├── build-linux.sh
├── cluster
├── commands.md
├── config
├── database
├── datastruct
├── go.mod
├── go.sum
├── interface
├── lib
├── logs
├── main.go
├── node1.conf
├── node2.conf
├── node3.conf
├── pubsub
├── redis
├── redis.conf
├── tcp
├── test.rdb
└── tmp

源码

主方法启动

func main() {// 打印标志print(banner)// 日志配置logger.Setup(&logger.Settings{Path:       "logs",Name:       "godis",Ext:        "log",TimeFormat: "2006-01-02",})// 配置文件configFilename := os.Getenv("CONFIG")// 获取redis.conf,如果没有获取到,则用默认参数if configFilename == "" {if fileExists("redis.conf") {config.SetupConfig("redis.conf")} else {config.Properties = defaultProperties}} else {config.SetupConfig(configFilename)}// 主程序启动,开启TCP服务err := tcp.ListenAndServeWithSignal(&tcp.Config{Address: fmt.Sprintf("%s:%d", config.Properties.Bind, config.Properties.Port),}, RedisServer.MakeHandler())if err != nil {logger.Error(err)}
}

TCP服务


// Config stores tcp server properties
type Config struct {Address    string        `yaml:"address"`MaxConnect uint32        `yaml:"max-connect"`Timeout    time.Duration `yaml:"timeout"`
}// ClientCounter Record the number of clients in the current Godis server
var ClientCounter int32// ListenAndServeWithSignal binds port and handle requests, blocking until receive stop signal
func ListenAndServeWithSignal(cfg *Config, handler tcp.Handler) error {// 创建两个通道closeChan := make(chan struct{})sigCh := make(chan os.Signal)signal.Notify(sigCh, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT)// 异步执行一个内置方法go func() {sig := <-sigChswitch sig {case syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT:closeChan <- struct{}{}}}()// 监听服务listener, err := net.Listen("tcp", cfg.Address)if err != nil {return err}//cfg.Address = listener.Addr().String()logger.Info(fmt.Sprintf("bind: %s, start listening...", cfg.Address))// 监听和服务ListenAndServe(listener, handler, closeChan)return nil
}// ListenAndServe binds port and handle requests, blocking until close
func ListenAndServe(listener net.Listener, handler tcp.Handler, closeChan <-chan struct{}) {// listen signalerrCh := make(chan error, 1)defer close(errCh)// 异步监听服务关闭信号go func() {// select 监听通道信息select {case <-closeChan:logger.Info("get exit signal")case er := <-errCh:logger.Info(fmt.Sprintf("accept error: %s", er.Error()))}logger.Info("shutting down...")// 关闭监听_ = listener.Close() // listener.Accept() will return err immediately// 关闭连接_ = handler.Close() // close connections}()// 创建一个上下文ctx := context.Background()var waitDone sync.WaitGroupfor {// 监听阻塞conn, err := listener.Accept()if err != nil {// learn from net/http/serve.go#Serve()if ne, ok := err.(net.Error); ok && ne.Timeout() {logger.Infof("accept occurs temporary error: %v, retry in 5ms", err)time.Sleep(5 * time.Millisecond)continue}// 错误信息传递errCh <- errbreak}// handlelogger.Info("accept link")// 统计客户端连接数ClientCounter++// 并发等待waitDone.Add(1)// 建立异步方法go func() {// 方法处理结束后,关闭defer func() {logger.Info("done...")waitDone.Done()// 建立连接后数量-1atomic.AddInt32(&ClientCounter, -1)}()logger.Info("handle data")// 服务端处理,上下文传递handler.Handle(ctx, conn)}()}waitDone.Wait()
}

TCP消息处理

package server/** A tcp.Handler implements redis protocol*/import ("context""io""net""strings""sync""github.com/hdt3213/godis/cluster""github.com/hdt3213/godis/config"database2 "github.com/hdt3213/godis/database""github.com/hdt3213/godis/interface/database""github.com/hdt3213/godis/lib/logger""github.com/hdt3213/godis/lib/sync/atomic""github.com/hdt3213/godis/redis/connection""github.com/hdt3213/godis/redis/parser""github.com/hdt3213/godis/redis/protocol"
)var (unknownErrReplyBytes = []byte("-ERR unknown\r\n")
)// 处理结构体
// Handler implements tcp.Handler and serves as a redis server
type Handler struct {// 活跃连接, 并发安全mapactiveConn sync.Map // *client -> placeholder// 存储引擎db      database.DBclosing atomic.Boolean // refusing new client and new request
}// 创建一个处理实例
// MakeHandler creates a Handler instance
func MakeHandler() *Handler {// redis的一个存储引擎var db database.DB// 创建是集群还是单例if config.Properties.ClusterEnable {db = cluster.MakeCluster()} else {db = database2.NewStandaloneServer()}return &Handler{db: db,}
}// 客户端关闭连接处理
func (h *Handler) closeClient(client *connection.Connection) {_ = client.Close()h.db.AfterClientClose(client)h.activeConn.Delete(client)
}// 处理接收到客户端的命令
// Handle receives and executes redis commands
func (h *Handler) Handle(ctx context.Context, conn net.Conn) {if h.closing.Get() {// closing handler refuse new connection_ = conn.Close()return}client := connection.NewConn(conn)// 存储一个客户端h.activeConn.Store(client, struct{}{})// 获取字符串ch := parser.ParseStream(conn)// 接收客户端数据for payload := range ch {// 遍历消息体if payload.Err != nil {// 错误信息处理,关闭连接if payload.Err == io.EOF ||payload.Err == io.ErrUnexpectedEOF ||strings.Contains(payload.Err.Error(), "use of closed network connection") {// connection closedh.closeClient(client)logger.Info("connection closed: " + client.RemoteAddr())return}// 协议错误处理,关闭连接// protocol errerrReply := protocol.MakeErrReply(payload.Err.Error())_, err := client.Write(errReply.ToBytes())if err != nil {h.closeClient(client)logger.Info("connection closed: " + client.RemoteAddr())return}continue}// 信息不可为空if payload.Data == nil {logger.Error("empty payload")continue}// 获取到客户端信息r, ok := payload.Data.(*protocol.MultiBulkReply)if !ok {logger.Error("require multi bulk protocol")continue}// 执行结果result := h.db.Exec(client, r.Args)// 结果回复if result != nil {_, _ = client.Write(result.ToBytes())} else {_, _ = client.Write(unknownErrReplyBytes)}}
}// Close stops handler
func (h *Handler) Close() error {logger.Info("handler shutting down...")h.closing.Set(true)// TODO: concurrent waith.activeConn.Range(func(key interface{}, val interface{}) bool {client := key.(*connection.Connection)_ = client.Close()return true})h.db.Close()return nil
}

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

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

相关文章

【网络安全】修改Host文件实现域名解析

场景 开发一个网站或者服务&#xff0c;需要在本地测试时&#xff0c;可以将线上的域名指向本地开发环境的IP地址。从而模拟真实环境中的域名访问&#xff0c;方便调试和开发。 步骤 1、以管理员身份打开命令提示符 2、编辑hosts文件&#xff1a; 输入以下命令打开hosts文…

Suno: AI音乐创作的新时代

名人说:一点浩然气,千里快哉风。 ——苏轼 创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 目录 一、什么是Suno?1、Suno2、应用场景二、如何使用Suno制作音乐?步骤1:注册并登录Suno平台步骤2:创建音乐项目步骤3:生成音乐片段三、Suno的影响很高兴你打开了…

【第六节】C/C++静态查找算法

目录 前言 一、搜索查找 二、查找算法 1. 线性查找&#xff08;Linear Search&#xff09; 2. 二分查找&#xff08;Binary Search&#xff09; 3. 插值查找&#xff08;Interpolation Search&#xff09; 4. 哈希查找&#xff08;Hash Search&#xff09; 5. Fibonacc…

C++感受12-Hello Object 派生版

不变的功能&#xff0c;希望直接复用原有代码&#xff1b;变化的功能&#xff0c;希望在分开的代码里实现。 派生的基本概念和目的如何定义派生类以及创建派生对象派生对象的生死过程 0. 课堂视频 ff14-HelloObject-派生版 1. 派生的基本概念与目的 编程&#xff0c;或者说软…

python 音频和视频合并自动裁剪

为了将音频和视频合并并自动裁剪&#xff0c;我们可以使用Python中的moviepy库。moviepy是一个强大的视频处理库&#xff0c;它允许我们进行剪辑、裁剪、合并等操作。 以下是一个详细的步骤和代码示例&#xff0c;说明如何使用moviepy来合并音频和视频&#xff0c;并自动裁剪它…

vue中的坑·

常规 1.使用watch时&#xff0c;immediate true会在dom挂载前执行 2.使用this.$attrs和props 可以获取上层非原生属性&#xff08;class/id&#xff09; 多层次嵌套引用 设置的时候直接赋值&#xff0c;修改的时候即使用的双向绑定加上$set / nextick / fouceUpdate都不会同步…

FastGPT 错误:Embedding API is not responding

一、FastGPT 报错 在调用 Embedding 模型对文档切片向量化的时候 FastGPT 出现如下错误。 [Error] 2024-07-01 08:41:00 Embedding API is not responding {message: <!doctype html><html lang="zh-CN"><head><meta charset="utf-8&qu…

HiBit Uninstaller:软件批量卸载,一触即得

名人说&#xff1a;莫道谗言如浪深&#xff0c;莫言迁客似沙沉。 ——刘禹锡《浪淘沙》 创作者&#xff1a;Code_流苏(CSDN)&#xff08;一个喜欢古诗词和编程的Coder&#x1f60a;&#xff09; 目录 一、软件介绍1、HiBit Uninstaller2、核心功能 二、下载安装1、下载2、安装 …

【基础篇】第2章 Elasticsearch安装与配置

2.1 环境准备 2.1.1 操作系统选择 Elasticsearch作为一个跨平台的搜索引擎&#xff0c;理论上支持所有能运行Java虚拟机的操作系统&#xff0c;包括但不限于Linux、macOS和Windows。Linux是生产环境中最为推荐的选择&#xff0c;因为它提供了更好的性能和稳定性。macOS适合开…

javascript/js中Array、Set、Map数据结构特性及用法

前言 本文温习前端开发中常见的3种数据结构&#xff1a;数组Array、有序集合Set、有序映射Map&#xff0c;并分别介绍其特性、用法示例 数组-Array 适用于存放和读取有序集合、不要求集合元素唯一性时&#xff1b;可通过索引快速访问元素&#xff0c;实现元素增删改查时 使…

山东省安管人员考核报名流程及免冠证件照处理方法

随着《交通运输工程施工单位主要负责人、项目负责人和专职安全生产管理人员安全生产考核管理办法》&#xff08;以下简称《办法》&#xff09;的发布&#xff0c;山东省的安管人员迎来了新的考核要求。本文将为您详细解读山东省安管人员考核的报名流程&#xff0c;并提供免冠证…

【MotionCap】搭建wsl2的pytorch环境

参考大神:wsl2-ubuntu版本 cuda下周cuda11.3 wget https://developer.download.nvidia.com/compute/cuda/11.3.0/local_installers/cuda_11.3.0_465.19.01_linux.run sudo sh cuda_11.3.0_465.19.01_linux.run cuda是开源的么?下15分钟

1、什么是SSD?

概念 SSD&#xff08;Solid State Drive&#xff09;固态硬盘&#xff0c;是以闪存为介质的存储设备&#xff1b;这里突出的重点是闪存。 闪存&#xff0c;也就是常说的flash&#xff0c;分为NOR 和 NAND&#xff1b; NOR的地址线和数据线分开&#xff0c;所以NOR芯片可以像…

vue html2canvas 将html转图片时遇到的问题解决

问题1&#xff1a; 场景为将富文本组件tinymce里的html内容转为图片&#xff0c;出现的问题是vue获取不到tinymce元素&#xff0c;无法直接使用html2canvas 解决1&#xff1a; 将富文本内容渲染出来&#xff0c;推荐做法是将提交按钮改为预览按钮&#xff0c;然后在另外的弹…

“一带一路”再奏强音!秘鲁总统博鲁阿尔特参访苏州金龙

6月27日下午&#xff0c;首次访华的秘鲁共和国总统博鲁阿尔特一行到苏州金龙参观访问&#xff0c;受到了苏州金龙总经理黄书平的热情接待。 黄书平&#xff08;左二&#xff09;向博鲁阿尔特&#xff08;右一&#xff09;介绍苏州金龙发展情况 从苏州金龙发展历程、产品技术研…

Python中的爬虫实战:百度知道爬虫

python作为一种强大的编程语言&#xff0c;可以帮助我们更便捷地获取互联网上的大量数据。其中&#xff0c;爬虫技术是极具代表性的一部分。爬虫可以在互联网上获取各种数据并进行分析&#xff0c;为我们提供大量的有价值的信息。在python中&#xff0c;爬虫技术也能够得到广泛…

使用Nginx反向代理KKFileView遇到问题

使用KKFileView 4.0 以上版本 在KKFileView官网上&#xff0c;关于使用Nginx代理&#xff0c;建议配置如下 一、修改Nacos 在Nginx的conf文件夹中修改 nginx.conf ,新加 红框内的IP地址为代理服务器地址&#xff08;即安装KKFileView的服务器地址&#xff09; 二、修改KKFil…

小程序打包

一、manifest.json文件添加小程序id 二、接口校验&#xff0c;后端接口添加正式上线&#xff0c;有域名的地址 然后到微信公众平台-开发管理-服务器域名处配置request合法域名&#xff0c;在 此处能够看到后端的baseUrl 三、项目部署 四、发版 在小程序编辑器里 此处可以在…

Android Studio 2023版本切换DNK版本

选择自己需要的版本下载 根目录下的配置路劲注意切换 build.gradle文件下的ndkVersion也要配好对应版本

数据可视化宝典:Jupyter Notebook与Matplotlib的完美融合

数据可视化宝典&#xff1a;Jupyter Notebook与Matplotlib的完美融合 在数据科学的世界里&#xff0c;数据可视化是一种艺术&#xff0c;它能够将复杂的数据转换为直观的图形&#xff0c;让洞察变得触手可及。Jupyter Notebook是一个强大的工具&#xff0c;它支持多种编程语言…