godis源码分析——Redis协议解析器

news2024/9/22 4:04:27

前言

redis这个目录下的所有代码就是为了一个事情,就是适配redis。

流程

redis下的基本流程

在这里插入图片描述

源码

在redis/client/client.go

主要是客户端处理

package client

const (
	created = iota
	running
	closed
)

type B struct {
	data   chan string
	ticker *time.Ticker
}

// Client is a pipeline mode redis client
type Client struct {
	conn net.Conn
	// 等待发送
	pendingReqs chan *request // wait to send
	// 等待响应
	waitingReqs chan *request // waiting response
	ticker      *time.Ticker
	addr        string

	status  int32
	working *sync.WaitGroup // its counter presents unfinished requests(pending and waiting)
}

// 这个一个发送到redis的请求结构
// request is a message sends to redis server
type request struct {
	id        uint64
	args      [][]byte
	reply     redis.Reply
	heartbeat bool
	waiting   *wait.Wait
	err       error
}

const (
	chanSize = 256
	maxWait  = 3 * time.Second
)

// MakeClient creates a new client
func MakeClient(addr string) (*Client, error) {
	conn, err := net.Dial("tcp", addr)
	if err != nil {
		return nil, err
	}
	return &Client{
		addr:        addr,
		conn:        conn,
		pendingReqs: make(chan *request, chanSize),
		waitingReqs: make(chan *request, chanSize),
		working:     &sync.WaitGroup{},
	}, nil
}

// 开始启动异步程序
// Start starts asynchronous goroutines
func (client *Client) Start() {
	client.ticker = time.NewTicker(10 * time.Second)
	// 每个方法都会监听channel
	go client.handleWrite()
	go client.handleRead()
	go client.heartbeat()
	atomic.StoreInt32(&client.status, running)
}

// 异步关闭客户端
// Close stops asynchronous goroutines and close connection
func (client *Client) Close() {
	atomic.StoreInt32(&client.status, closed)
	client.ticker.Stop()
	// stop new request
	close(client.pendingReqs)

	// wait stop process
	client.working.Wait()

	// clean
	_ = client.conn.Close()
	close(client.waitingReqs)
}

// 重新连接
func (client *Client) reconnect() {
	logger.Info("reconnect with: " + client.addr)
	_ = client.conn.Close() // ignore possible errors from repeated closes

	var conn net.Conn
	for i := 0; i < 3; i++ {
		var err error
		conn, err = net.Dial("tcp", client.addr)
		if err != nil {
			logger.Error("reconnect error: " + err.Error())
			time.Sleep(time.Second)
			continue
		} else {
			break
		}
	}
	if conn == nil { // reach max retry, abort
		client.Close()
		return
	}
	client.conn = conn

	close(client.waitingReqs)
	for req := range client.waitingReqs {
		req.err = errors.New("connection closed")
		req.waiting.Done()
	}
	client.waitingReqs = make(chan *request, chanSize)
	// restart handle read
	go client.handleRead()
}

// 监听发送心跳
func (client *Client) heartbeat() {
	for range client.ticker.C {
		client.doHeartbeat()
	}
}

// 写入监听
func (client *Client) handleWrite() {
	for req := range client.pendingReqs {
		client.doRequest(req)
	}
}

// 发送一个请求到redis服务器
// Send sends a request to redis server
func (client *Client) Send(args [][]byte) redis.Reply {
	if atomic.LoadInt32(&client.status) != running {
		return protocol.MakeErrReply("client closed")
	}
	req := &request{
		args:      args,
		heartbeat: false,
		waiting:   &wait.Wait{},
	}
	req.waiting.Add(1)
	client.working.Add(1)
	defer client.working.Done()
	// 放入
	client.pendingReqs <- req
	timeout := req.waiting.WaitWithTimeout(maxWait)
	if timeout {
		return protocol.MakeErrReply("server time out")
	}
	if req.err != nil {
		return protocol.MakeErrReply("request failed " + req.err.Error())
	}
	return req.reply
}

// 心跳
func (client *Client) doHeartbeat() {
	request := &request{
		args:      [][]byte{[]byte("PING")},
		heartbeat: true,
		waiting:   &wait.Wait{},
	}
	request.waiting.Add(1)
	client.working.Add(1)
	defer client.working.Done()
	client.pendingReqs <- request
	request.waiting.WaitWithTimeout(maxWait)
}

func (client *Client) doRequest(req *request) {
	if req == nil || len(req.args) == 0 {
		return
	}
	// 数据转换为byte
	re := protocol.MakeMultiBulkReply(req.args)
	bytes := re.ToBytes()
	var err error
	// 三次重试
	for i := 0; i < 3; i++ { // only retry, waiting for handleRead
		_, err = client.conn.Write(bytes)
		if err == nil ||
			(!strings.Contains(err.Error(), "timeout") && // only retry timeout
				!strings.Contains(err.Error(), "deadline exceeded")) {
			break
		}
	}
	if err == nil {
		// 成功发送通知
		client.waitingReqs <- req
	} else {
		req.err = err
		req.waiting.Done()
	}
}

// 完成请求
func (client *Client) finishRequest(reply redis.Reply) {
	defer func() {
		if err := recover(); err != nil {
			debug.PrintStack()
			logger.Error(err)
		}
	}()
	request := <-client.waitingReqs
	if request == nil {
		return
	}
	request.reply = reply
	if request.waiting != nil {
		request.waiting.Done()
	}
}

// 处理响应数据
func (client *Client) handleRead() {
	// 数据转义
	ch := parser.ParseStream(client.conn)
	for payload := range ch {
		// 检查消息体有没有错误
		if payload.Err != nil {
			status := atomic.LoadInt32(&client.status)
			if status == closed {
				return
			}
			client.reconnect()
			return
		}
		client.finishRequest(payload.Data)
	}
}

在redis/conn/conn.go

TCP连接方法管理

import (
	"net"
	"sync"
	"time"

	"github.com/hdt3213/godis/lib/logger"
	"github.com/hdt3213/godis/lib/sync/wait"
)

const (
	// flagSlave means this a connection with slave
	flagSlave = uint64(1 << iota)
	// flagSlave means this a connection with master
	flagMaster
	// flagMulti means this connection is within a transaction
	flagMulti
)

// Connection represents a connection with a redis-cli
type Connection struct {
	conn net.Conn

	// wait until finish sending data, used for graceful shutdown
	sendingData wait.Wait

	// lock while server sending response
	mu    sync.Mutex
	flags uint64

	// subscribing channels
	subs map[string]bool

	// password may be changed by CONFIG command during runtime, so store the password
	password string

	// queued commands for `multi`
	queue    [][][]byte
	watching map[string]uint32
	txErrors []error

	// selected db
	selectedDB int
}

// 连接池
var connPool = sync.Pool{
	New: func() interface{} {
		return &Connection{}
	},
}

// 返回远程地址
// RemoteAddr returns the remote network address
func (c *Connection) RemoteAddr() string {
	return c.conn.RemoteAddr().String()
}

// Close disconnect with the client
func (c *Connection) Close() error {
	c.sendingData.WaitWithTimeout(10 * time.Second)
	_ = c.conn.Close()
	c.subs = nil
	c.password = ""
	c.queue = nil
	c.watching = nil
	c.txErrors = nil
	c.selectedDB = 0
	connPool.Put(c)
	return nil
}

// 创建一个连接实例
// NewConn creates Connection instance
func NewConn(conn net.Conn) *Connection {
	// 从线程池去
	c, ok := connPool.Get().(*Connection)
	if !ok {
		logger.Error("connection pool make wrong type")
		return &Connection{
			conn: conn,
		}
	}
	c.conn = conn
	return c
}

// Write sends response to client over tcp connection
func (c *Connection) Write(b []byte) (int, error) {
	if len(b) == 0 {
		return 0, nil
	}
	c.sendingData.Add(1)
	defer func() {
		c.sendingData.Done()
	}()
	return c.conn.Write(b)
}

// 获取连接名称
func (c *Connection) Name() string {
	if c.conn != nil {
		return c.conn.RemoteAddr().String()
	}
	return ""
}

// 订阅放入map
// Subscribe add current connection into subscribers of the given channel
func (c *Connection) Subscribe(channel string) {
	c.mu.Lock()
	defer c.mu.Unlock()

	if c.subs == nil {
		c.subs = make(map[string]bool)
	}
	c.subs[channel] = true
}

// 订阅删除
// UnSubscribe removes current connection into subscribers of the given channel
func (c *Connection) UnSubscribe(channel string) {
	c.mu.Lock()
	defer c.mu.Unlock()

	if len(c.subs) == 0 {
		return
	}
	delete(c.subs, channel)
}

// 获取订阅集合长度
// SubsCount returns the number of subscribing channels
func (c *Connection) SubsCount() int {
	return len(c.subs)
}

// GetChannels returns all subscribing channels
func (c *Connection) GetChannels() []string {
	if c.subs == nil {
		return make([]string, 0)
	}
	channels := make([]string, len(c.subs))
	i := 0
	for channel := range c.subs {
		channels[i] = channel
		i++
	}
	return channels
}

// 设置密码
// SetPassword stores password for authentication
func (c *Connection) SetPassword(password string) {
	c.password = password
}

// 获取密码
// GetPassword get password for authentication
func (c *Connection) GetPassword() string {
	return c.password
}

// 获取可变状态
// InMultiState tells is connection in an uncommitted transaction
func (c *Connection) InMultiState() bool {
	return c.flags&flagMulti > 0
}

// 设置可变状态
// SetMultiState sets transaction flag
func (c *Connection) SetMultiState(state bool) {
	if !state { // reset data when cancel multi
		c.watching = nil
		c.queue = nil
		c.flags &= ^flagMulti // clean multi flag
		return
	}
	c.flags |= flagMulti
}

// 返回当前事务的队列命令
// GetQueuedCmdLine returns queued commands of current transaction
func (c *Connection) GetQueuedCmdLine() [][][]byte {
	return c.queue
}

// 命令加入队列
// EnqueueCmd  enqueues command of current transaction
func (c *Connection) EnqueueCmd(cmdLine [][]byte) {
	c.queue = append(c.queue, cmdLine)
}

// AddTxError stores syntax error within transaction
func (c *Connection) AddTxError(err error) {
	c.txErrors = append(c.txErrors, err)
}

// GetTxErrors returns syntax error within transaction
func (c *Connection) GetTxErrors() []error {
	return c.txErrors
}

// ClearQueuedCmds clears queued commands of current transaction
func (c *Connection) ClearQueuedCmds() {
	c.queue = nil
}

// GetWatching returns watching keys and their version code when started watching
func (c *Connection) GetWatching() map[string]uint32 {
	if c.watching == nil {
		c.watching = make(map[string]uint32)
	}
	return c.watching
}

// GetDBIndex returns selected db
func (c *Connection) GetDBIndex() int {
	return c.selectedDB
}

// SelectDB selects a database
func (c *Connection) SelectDB(dbNum int) {
	c.selectedDB = dbNum
}

func (c *Connection) SetSlave() {
	c.flags |= flagSlave
}

func (c *Connection) IsSlave() bool {
	return c.flags&flagSlave > 0
}

func (c *Connection) SetMaster() {
	c.flags |= flagMaster
}

func (c *Connection) IsMaster() bool {
	return c.flags&flagMaster > 0
}

在redis/conn/fake.go

假连接,用于测试

在redis/parser/parser.go

用于解析客户端发来的数据

package parser

import (
	"bufio"
	"bytes"
	"errors"
	"io"
	"runtime/debug"
	"strconv"
	"strings"

	"github.com/hdt3213/godis/interface/redis"
	"github.com/hdt3213/godis/lib/logger"
	"github.com/hdt3213/godis/redis/protocol"
)

// 消息体结构
// Payload stores redis.Reply or error
type Payload struct {
	Data redis.Reply
	Err  error
}

// 解析从io流的数据
// ParseStream reads data from io.Reader and send payloads through channel
func ParseStream(reader io.Reader) <-chan *Payload {
	ch := make(chan *Payload)
	go parse0(reader, ch)
	return ch
}

// 解析byte
// ParseBytes reads data from []byte and return all replies
func ParseBytes(data []byte) ([]redis.Reply, error) {
	ch := make(chan *Payload)
	reader := bytes.NewReader(data)
	go parse0(reader, ch)
	var results []redis.Reply
	for payload := range ch {
		if payload == nil {
			return nil, errors.New("no protocol")
		}
		if payload.Err != nil {
			if payload.Err == io.EOF {
				break
			}
			return nil, payload.Err
		}
		results = append(results, payload.Data)
	}
	return results, nil
}

// 解析第一个消息体
// ParseOne reads data from []byte and return the first payload
func ParseOne(data []byte) (redis.Reply, error) {
	ch := make(chan *Payload)
	reader := bytes.NewReader(data)
	go parse0(reader, ch)
	payload := <-ch // parse0 will close the channel
	if payload == nil {
		return nil, errors.New("no protocol")
	}
	return payload.Data, payload.Err
}

// 私有方法,
func parse0(rawReader io.Reader, ch chan<- *Payload) {
	// 最后判断有无错误,有则打印日志
	defer func() {
		if err := recover(); err != nil {
			logger.Error(err, string(debug.Stack()))
		}
	}()
	// 解析流
	reader := bufio.NewReader(rawReader)
	for {
		line, err := reader.ReadBytes('\n')
		if err != nil {
			// 异常处理
			ch <- &Payload{Err: err}
			close(ch)
			return
		}
		// 解析长度
		length := len(line)
		// 过短异常
		if length <= 2 || line[length-2] != '\r' {
			// there are some empty lines within replication traffic, ignore this error
			//protocolError(ch, "empty line")
			continue
		}
		line = bytes.TrimSuffix(line, []byte{'\r', '\n'})
		// 根据不同的字符,做不同的解析方法,ASCII判断
		switch line[0] {
		case '+':
			content := string(line[1:])
			ch <- &Payload{
				Data: protocol.MakeStatusReply(content),
			}
			if strings.HasPrefix(content, "FULLRESYNC") {
				err = parseRDBBulkString(reader, ch)
				if err != nil {
					ch <- &Payload{Err: err}
					close(ch)
					return
				}
			}
		case '-':
			ch <- &Payload{
				Data: protocol.MakeErrReply(string(line[1:])),
			}
		case ':':
			value, err := strconv.ParseInt(string(line[1:]), 10, 64)
			if err != nil {
				protocolError(ch, "illegal number "+string(line[1:]))
				continue
			}
			ch <- &Payload{
				Data: protocol.MakeIntReply(value),
			}
		case '$':
			err = parseBulkString(line, reader, ch)
			if err != nil {
				ch <- &Payload{Err: err}
				close(ch)
				return
			}
		case '*':
			err = parseArray(line, reader, ch)
			if err != nil {
				ch <- &Payload{Err: err}
				close(ch)
				return
			}
		default:
			args := bytes.Split(line, []byte{' '})
			ch <- &Payload{
				Data: protocol.MakeMultiBulkReply(args),
			}
		}
	}
}

// 解析字符串
func parseBulkString(header []byte, reader *bufio.Reader, ch chan<- *Payload) error {
	strLen, err := strconv.ParseInt(string(header[1:]), 10, 64)
	if err != nil || strLen < -1 {
		protocolError(ch, "illegal bulk string header: "+string(header))
		return nil
	} else if strLen == -1 {
		ch <- &Payload{
			Data: protocol.MakeNullBulkReply(),
		}
		return nil
	}
	body := make([]byte, strLen+2)
	_, err = io.ReadFull(reader, body)
	if err != nil {
		return err
	}
	ch <- &Payload{
		Data: protocol.MakeBulkReply(body[:len(body)-2]),
	}
	return nil
}

// RDB和后续AOF之间没有CRLF,因此需要区别对待
// there is no CRLF between RDB and following AOF, therefore it needs to be treated differently
func parseRDBBulkString(reader *bufio.Reader, ch chan<- *Payload) error {
	header, err := reader.ReadBytes('\n')
	header = bytes.TrimSuffix(header, []byte{'\r', '\n'})
	if len(header) == 0 {
		return errors.New("empty header")
	}
	strLen, err := strconv.ParseInt(string(header[1:]), 10, 64)
	if err != nil || strLen <= 0 {
		return errors.New("illegal bulk header: " + string(header))
	}
	body := make([]byte, strLen)
	_, err = io.ReadFull(reader, body)
	if err != nil {
		return err
	}
	ch <- &Payload{
		Data: protocol.MakeBulkReply(body[:len(body)]),
	}
	return nil
}

func parseArray(header []byte, reader *bufio.Reader, ch chan<- *Payload) error {
	nStrs, err := strconv.ParseInt(string(header[1:]), 10, 64)
	// nStrs > 0为合法
	if err != nil || nStrs < 0 {
		protocolError(ch, "illegal array header "+string(header[1:]))
		return nil
	} else if nStrs == 0 {
		ch <- &Payload{
			Data: protocol.MakeEmptyMultiBulkReply(),
		}
		return nil
	}
	// 消息合法判断
	lines := make([][]byte, 0, nStrs)
	for i := int64(0); i < nStrs; i++ {
		var line []byte
		line, err = reader.ReadBytes('\n')
		if err != nil {
			return err
		}
		length := len(line)
		if length < 4 || line[length-2] != '\r' || line[0] != '$' {
			protocolError(ch, "illegal bulk string header "+string(line))
			break
		}
		strLen, err := strconv.ParseInt(string(line[1:length-2]), 10, 64)
		if err != nil || strLen < -1 {
			protocolError(ch, "illegal bulk string length "+string(line))
			break
		} else if strLen == -1 {
			lines = append(lines, []byte{})
		} else {
			body := make([]byte, strLen+2)
			_, err := io.ReadFull(reader, body)
			if err != nil {
				return err
			}
			lines = append(lines, body[:len(body)-2])
		}
	}
	// 合法消息装入通道
	ch <- &Payload{
		Data: protocol.MakeMultiBulkReply(lines),
	}
	return nil
}

func protocolError(ch chan<- *Payload, msg string) {
	err := errors.New("protocol error: " + msg)
	ch <- &Payload{Err: err}
}

在redis/protocol/asserts/asserts.go

用于测试检查

在redis/protocol/consts.go

定义的一些常量

在redis/protocol/errors.go

定义的一些错误

在redis/protocol/reply.go

协议消息返回

在redis/server/server.go

TCP服务接收到连接后,异步拉起服务,用于客户端的消息处理

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

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

相关文章

ubuntu 上vscode +cmake的debug调试配置方法

在ubuntu配置pcl点云库以及opencv库的时候&#xff0c;需要在CMakeLists.txt中加入相应的代码。配置完成后&#xff0c;无法调试&#xff0c;与在windows上体验vs studio差别有点大。 找了好多调试debug配置方法&#xff0c;最终能用的有几种&#xff0c;但是有一种特别好用&a…

‘wget‘ 不是内部或外部命令,也不是可运行的程序

在Windows环境下创建了虚拟环境并安装了wget包&#xff0c;但在使用该命令的时候仍然报错&#xff0c;‘wget’ 不是内部或外部命令,也不是可运行的程序 解决方案&#xff1a; 去官网下载对应位数的.exe文件&#xff0c;将其放在C:\Windows\System32目录下即可, 别下错版本&a…

常见WAF拦截页面总结

(1) D盾 (2) 云锁 (3) UPUPW安全防护 (4) 宝塔网站防火墙 (5) 网防G01 (6) 护卫神 (7) 网站安全狗 (8) 智创防火墙 (9) 360主机卫士或360webscan (10) 西数WTS-WAF (11) Naxsi WAF (12) 腾讯云 (13) 腾讯宙斯盾 (14) 百度云 图片 (15) 华为云 (16) 网宿云 (17) 创宇盾 图片 (…

首届UTON区块链开发者计划大会在马来西亚圆满落幕

7月9日&#xff0c;首届UTON区块链开发者计划大会在马来西亚吉隆坡成功举办&#xff01; 来自全球顶尖的行业领袖、技术精英和众多区块链爱好者参与了此次盛会&#xff0c;也标志着UTON区块链生态进入了一个全新的发展阶段。 会上&#xff0c;UTON区块链创始人之一唐毅先生以“…

leetcode:1332. 删除回文子序列(python3解法)

难度&#xff1a;简单 给你一个字符串 s&#xff0c;它仅由字母 a 和 b 组成。每一次删除操作都可以从 s 中删除一个回文 子序列。 返回删除给定字符串中所有字符&#xff08;字符串为空&#xff09;的最小删除次数。 「子序列」定义&#xff1a;如果一个字符串可以通过删除原字…

Qt开发 | qss介绍及控件应用 | qss加载方式 | 控件提升 | 鼠标位置与控件位置 | 搜索编辑框 | tab在左文本水平的tabWidget

文章目录 一、qss简介与应用二、QLineEdit qss介绍与使用三、QPushButton qss1.常用qss1.1 基本样式表1.2 背景图片1.3 图片在左文字在右 2.点击按钮弹出菜单以及右侧箭头样式设置3.鼠标悬浮按钮弹出对话框 四、QCheckBox qss妙用&#xff1a;实时打开关闭状态按钮五、QComboBo…

react基础语法,模板语法,ui渲染,jsx,useState状态管理

创建一个react应用 这里使用create-react-app的脚手架构建项目&#xff08;结构简洁&#xff0c;基于webpack-cli&#xff09;&#xff0c; npx create-react-app [项目名称] 使用其他脚手架构建项目可以参考&#xff1a;react框架&#xff0c;使用vite和nextjs构建react项目…

ch552g中使用SPI进行主从机通信时发现的问题

参考 基本硬件准备 两块独立的ch552g的板子&#xff0c;开始连接时数据传输出现数据错误&#xff0c;本来猜想是通信线连接问题&#xff0c;后来用了较短的连接线依然没有改善。 SPI通信的认知 SPI一般都是全双工实时通信&#xff0c;所以在发送数据时一般有短暂的停留使得…

echarts使用自定义图形实现3D柱状图

先看下效果吧 实现思路 使用graphic创建并注册自定义图形。根据每组的数据值&#xff0c;得到一个对应的点&#xff0c;从点出发用canvas绘制一组图形&#xff0c;分别为 顶部的菱形 const CubeTop echarts.graphic.extendShape({buildPath: function (ctx, shape) {const c1…

【ComfyUI的API接口调用示例】

ComfyUI的API接口调用示例 本文目的 本文调用接口示例主要指导需要调用ComfyUI的开发者如何调用ComfyUI官方的API接口提交任务、查询历史、获取绘画视频结果等。 阅读本文的前提是你本地已经安装了ComfyUI&#xff0c;并且对工作流绘画和生成视频已经有所了解。注意如图右边栏…

印刷企业如何判断数字工厂管理系统的实施周期

在数字化转型的浪潮中&#xff0c;印刷企业正积极拥抱新技术以提升生产效率、优化成本结构并增强市场竞争力。数字工厂管理系统的引入&#xff0c;作为这一转型的关键步骤&#xff0c;不仅能够实现生产流程的自动化、智能化监控&#xff0c;还能显著提升数据分析能力和决策效率…

VScode 格式化插件Prettier设置无效

VScode在配置格式化代码的插件的时候&#xff0c;可以选择Prettier或者ESlint等插件 比如选择Prettier格式化代码 在某文件修改代码之后&#xff0c;ctrls 保存代码&#xff0c;保存之后会自动格式化代码&#xff0c;但是我们发现控制台有报错 为什么已经设置了格式化插件为Pr…

软考《信息系统运行管理员》-3.2信息系统设施运维的环境管理

3.2信息系统设施运维的环境管理 1 计算机机房的选址要求 电子计算机机房地理位置 选择水源充足&#xff0c;电子比较稳定可靠&#xff0c;交通通信方便&#xff0c;自然环境清洁的地点要远离产生粉尘、油烟、有害气体以及生产或存储具有腐蚀性、易燃、易爆物品的工厂、仓库、…

Vue+SpringBoot实现仿网盘项目

目录 一、效果展示 二、前端代码 三、后端代码及核心解释 四、进阶开发与思路 一、效果展示 1.1读取文件夹内的文件 1.2删除功能 1.3 上传文件 1.4 文件下载 对应的网盘实际地址与对应下载内容&#xff1a; 二、前端代码 2.1 创建vue项目&#xff08;需要有vuex与router&…

【C++】类和对象--类,实例化,this指针

文章目录 前言一、类1.1 类的定义1.2 类的书写和使用1.3 访问限定符1.4 类域 二、实例化2.1 实例化概念2.2 对象大小 三.this指针总结 前言 前面的几篇文章我们介绍了命名空间&#xff0c;inline&#xff0c;nullptr等C 中常见的的基础概念。今天的文章我们来介绍一些C中类与对…

一款24小时实时检测的六氟化硫气体泄漏报警系统

尽管当前工业生产模式越来越趋于自动化、智能化&#xff0c;但安全生产仍然是时下屡被提及的话题。在配电室等使用六氟化硫气体的众多领域中&#xff0c;由于气体泄漏而引发的中毒、火灾、爆炸、窒息事故仍高发频发。因此&#xff0c;安装六氟化硫气体泄漏报警监测系统仍是企业…

使用PEFT库进行ChatGLM3-6B模型的QLORA高效微调

PEFT库进行ChatGLM3-6B模型QLORA高效微调 QLORA微调ChatGLM3-6B模型安装相关库使用ChatGLM3-6B模型GPU显存占用准备数据集加载数据集数据处理数据集处理加载量化模型-4bit预处理量化模型配置LoRA适配器训练超参数配置开始训练保存LoRA模型模型推理合并模型使用微调后的模型 QLO…

UE5 本地化多语言方案

导入插件&#xff1a; https://www.unrealengine.com/marketplace/zh-CN/product/07e1d9bd9ced444c9b2a7e232161f74d​www.unrealengine.com/marketplace/zh-CN/product/07e1d9bd9ced444c9b2a7e232161f74d 打开测试关卡 打开插件下图目录&#xff0c;csv文件可以添加多个&…

MD5加密和注册页面的编写

MD5加密 1.导入包 npm install --save ts-md5 2.使用方式 import { Md5 } from ts-md5; //md5加密后的密码 const md5PwdMd5.hashStr("123456").toUpperCase(); 遇见的问题及用到的技术 注册页面 register.vue代码 <template><div class"wappe…

模型评估、交叉验证

目录 一、模型评估&#xff1a;二、交叉验证&#xff1a; 一、模型评估&#xff1a; 模型评估用来检验模型的预测精度。 首先数据集分为训练集和测试集两部分&#xff0c;使用训练集进行模型的训练&#xff0c;使用测试集进行模型的评估。 注意&#xff0c;模型评估阶段应该…