Redis内存数据库示例分析

目录
  • redies dict字典
  • Redis的DB实现
  • 具体的实现器
  • Redis持久化Aof

redies dict字典

这是 Redis 最底层的结构,比如 1个DB 下面有 16个Dict

1. 使用接口方式, 基础实现是simple_dict ,sync_dict ,后续用户可以根据自己的需求进行自定义的实现属于自己的Dict , 在 forEach 方法 支持匿名函数方式
type Dict interface {
  Get(key string) (val interface {}, exists bool)
  Len()
  // 回复上次 put 放入
  Put(key string, val interface{}) (result int)
  PutIfAbsent(key string, val interface {}) (result int)
  PutIfExists(key string, val interface{}) (result int)
  Remove(key string) (result int)
  ForEach(consumer Consumer) // 传入是方法, 返回 bool = true j继续遍历
  Keys() []string
  RandomKeys(limit int) []string
  // 返回多个不重复的键
  RandomDistinctKeys(limit int) []string
  Clear()
}
// Consumer is used to traversal dict, if it returns false the traversal will be break
type Consumer func(key string, val interface{}) bool
package dict
// SimpleDict wraps a map, it is not thread safe
type SimpleDict struct {
	m map[string]interface{}
}
// MakeSimple makes a new map
func MakeSimple() *SimpleDict {
	return &SimpleDict{
		m: make(map[string]interface{}),
	}
}
// Get returns the binding value and whether the key is exist
func (dict *SimpleDict) Get(key string) (val interface{}, exists bool) {
	val, ok := dict.m[key]
	return val, ok
}
// Len returns the number of dict
func (dict *SimpleDict) Len() int {
	if dict.m == nil {
		panic("m is nil")
	}
	return len(dict.m)
}
// Put puts key value into dict and returns the number of new inserted key-value
func (dict *SimpleDict) Put(key string, val interface{}) (result int) {
	_, existed := dict.m[key]
	dict.m[key] = val
	if existed {
		return 0
	}
	return 1
}
// PutIfAbsent puts value if the key is not exists and returns the number of updated key-value
func (dict *SimpleDict) PutIfAbsent(key string, val interface{}) (result int) {
	_, existed := dict.m[key]
	if existed {
		return 0
	}
	dict.m[key] = val
	return 1
}
// PutIfExists puts value if the key is exist and returns the number of inserted key-value
func (dict *SimpleDict) PutIfExists(key string, val interface{}) (result int) {
	_, existed := dict.m[key]
	if existed {
		dict.m[key] = val
		return 1
	}
	return 0
}
// Remove removes the key and return the number of deleted key-value
func (dict *SimpleDict) Remove(key string) (result int) {
	_, existed := dict.m[key]
	delete(dict.m, key)
	if existed {
		return 1
	}
	return 0
}
// Keys returns all keys in dict
func (dict *SimpleDict) Keys() []string {
	result := make([]string, len(dict.m))
	i := 0
	for k := range dict.m {
		result[i] = k
	}
	return result
}
// ForEach traversal the dict
func (dict *SimpleDict) ForEach(consumer Consumer) {
	for k, v := range dict.m {
		if !consumer(k, v) {
			break
		}
	}
}
// RandomKeys randomly returns keys of the given number, may contain duplicated key
func (dict *SimpleDict) RandomKeys(limit int) []string {
	result := make([]string, limit)
	for i := 0; i < limit; i++ {
		for k := range dict.m {
			result[i] = k
			break
		}
	}
	return result
}
// RandomDistinctKeys randomly returns keys of the given number, won't contain duplicated key
func (dict *SimpleDict) RandomDistinctKeys(limit int) []string {
	size := limit
	if size > len(dict.m) {
		size = len(dict.m)
	}
	result := make([]string, size)
	i := 0
	for k := range dict.m {
		if i == limit {
			break
		}
		result[i] = k
		i++
	}
	return result
}
// Clear removes all keys in dict
func (dict *SimpleDict) Clear() {
	*dict = *MakeSimple()
}

Redis的DB实现

针对其中 Command 的实现, ping, set ,Get 有具体的实现方法, 采用策略模式形式, 不同的方法 有自己的 Executor 执行器

// 指令与结构体之间的关系
var cmdTable = make(map[string]*command)
type command struct {
	executor ExecFunc
	arity    int // allow number of args, arity < 0 means len(args) >= -arity
}
// RegisterCommand registers a new command
// arity means allowed number of cmdArgs, arity < 0 means len(args) >= -arity.
// for example: the arity of `get` is 2, `mget` is -2
func RegisterCommand(name string, executor ExecFunc, arity int) {
	name = strings.ToLower(name)
	cmdTable[name] = &command{
		executor: executor,
		arity:    arity,
	}
}
// Package database is a memory database with redis compatible interface
package database
import (
	"go-redis/datastruct/dict"
	"go-redis/interface/database"
	"go-redis/interface/resp"
	"go-redis/resp/reply"
	"strings"
)
// DB stores data and execute user's commands
type DB struct {
	index int
	// key -> DataEntity
	data dict.Dict // 底层的实现可以进行切换
}
// ExecFunc is interface for command executor, redis 的 指令实现, 所有的实现 协程这种形式
// args don't include cmd line
type ExecFunc func(db *DB, args [][]byte) resp.Reply
// CmdLine is alias for [][]byte, represents a command line
type CmdLine = [][]byte
// makeDB create DB instance
func makeDB() *DB {
	db := &DB{
		data: dict.MakeSyncDict(), // 使用的 sync 的 dict
	}
	return db
}
// Exec executes command within one database
func (db *DB) Exec(c resp.Connection, cmdLine [][]byte) resp.Reply {
	cmdName := strings.ToLower(string(cmdLine[0]))
	cmd, ok := cmdTable[cmdName]
	if !ok {
		return reply.MakeErrReply("ERR unknown command '" + cmdName + "'")
	}
	if !validateArity(cmd.arity, cmdLine) {
		return reply.MakeArgNumErrReply(cmdName)
	}
	fun := cmd.executor
	return fun(db, cmdLine[1:])
}
func validateArity(arity int, cmdArgs [][]byte) bool {
	argNum := len(cmdArgs)
	if arity >= 0 {
		return argNum == arity
	}
	return argNum >= -arity
}
/* ---- data Access ----- */
// GetEntity returns DataEntity bind to given key
func (db *DB) GetEntity(key string) (*database.DataEntity, bool) {
	raw, ok := db.data.Get(key)
	if !ok {
		return nil, false
	}
	entity, _ := raw.(*database.DataEntity)
	return entity, true
}
// PutEntity a DataEntity into DB
func (db *DB) PutEntity(key string, entity *database.DataEntity) int {
	return db.data.Put(key, entity)
}
// PutIfExists edit an existing DataEntity
func (db *DB) PutIfExists(key string, entity *database.DataEntity) int {
	return db.data.PutIfExists(key, entity)
}
// PutIfAbsent insert an DataEntity only if the key not exists
func (db *DB) PutIfAbsent(key string, entity *database.DataEntity) int {
	return db.data.PutIfAbsent(key, entity)
}
// Remove the given key from db
func (db *DB) Remove(key string) {
	db.data.Remove(key)
}
// Removes the given keys from db
func (db *DB) Removes(keys ...string) (deleted int) {
	deleted = 0
	for _, key := range keys {
		_, exists := db.data.Get(key)
		if exists {
			db.Remove(key)
			deleted++
		}
	}
	return deleted
}
// Flush clean database
func (db *DB) Flush() {
	db.data.Clear()
}

具体的实现器

// Ping Executor
func Ping(db *DB, args [][]byte) resp.Reply {
    if len(args) == 0 {
        return &reply.PongReply{}
    } else if len(args) == 1 {
        return reply.MakeStatusReply(string(args[0]))
    } else {
        return reply.MakeErrReply("ERR wrong number of arguments for 'ping' command")
    }
}
// 初始化 方法
func init() {
    RegisterCommand("ping", Ping, -1)
}

Redis持久化Aof

AOF 则以协议文本的方式,将所有对数据库进行过写入的命令(及其参数)记录到 AOF 文件,以此达到记录数据库状态的目的.

package aof
import (
	"go-redis/config"
	databaseface "go-redis/interface/database"
	"go-redis/lib/logger"
	"go-redis/lib/utils"
	"go-redis/resp/connection"
	"go-redis/resp/parser"
	"go-redis/resp/reply"
	"io"
	"os"
	"strconv"
)
// CmdLine is alias for [][]byte, represents a command line
type CmdLine = [][]byte
const (
	aofQueueSize = 1 << 16
)
type payload struct {
	cmdLine CmdLine
	dbIndex int
}
// AofHandler receive msgs from channel and write to AOF file
type AofHandler struct {
	db          databaseface.Database
	aofChan     chan *payload
	aofFile     *os.File
	aofFilename string
	currentDB   int
}
// NewAOFHandler creates a new aof.AofHandler
func NewAOFHandler(db databaseface.Database) (*AofHandler, error) {
	handler := &AofHandler{}
	handler.aofFilename = config.Properties.AppendFilename
	handler.db = db
	// init Load Aof to Memory
	handler.LoadAof()
	aofFile, err := os.OpenFile(handler.aofFilename, os.O_APPEND|os.O_CREATE|os.O_RDWR, 0600)
	if err != nil {
		return nil, err
	}
	handler.aofFile = aofFile
	handler.aofChan = make(chan *payload, aofQueueSize)
	go func() {
		handler.handleAof()
	}()
	return handler, nil
}
func (handler *AofHandler) AddAof(dbIndex int, cmdLine CmdLine) {
	if config.Properties.AppendOnly && handler.aofChan != nil {
		handler.aofChan <- &payload{
			cmdLine: cmdLine,
			dbIndex: dbIndex,
		}
	}
}
// LoadAof read aof file 数据回放的功能
func (handler *AofHandler) LoadAof() {
	file, err := os.Open(handler.aofFilename)
	if err != nil {
		logger.Warn(err)
		return
	}
	defer func(file *os.File) {
		err := file.Close()
		if err != nil {
		}
	}(file)
	ch := parser.ParseStream(file)
	fakeConn := &connection.Connection{} // only used for save dbIndex
	// LoadAof recover data
	for p := range ch {
		if p.Err != nil {
			if p.Err == io.EOF {
				break
			}
			logger.Error("parse error: " + p.Err.Error())
			continue
		}
		if p.Data == nil {
			logger.Error("empty payload")
			continue
		}
		r, ok := p.Data.(*reply.MultiBulkReply)
		if !ok {
			logger.Error("require multi bulk reply")
			continue
		}
		ret := handler.db.Exec(fakeConn, r.Args)
		if reply.IsErrorReply(ret) {
			logger.Error("exec err", err)
		}
	}
}
// handleAof listen aof channel and write into file aof 异步形式的落盘
func (handler *AofHandler) handleAof() {
	handler.currentDB = 0
	for p := range handler.aofChan {
		if p.dbIndex != handler.currentDB {
			// select db
			data := reply.MakeMultiBulkReply(utils.ToCmdLine("SELECT", strconv.Itoa(p.dbIndex))).ToBytes()
			_, err := handler.aofFile.Write(data)
			if err != nil {
				logger.Warn(err)
				continue // skip this command
			}
			handler.currentDB = p.dbIndex
		}
		data := reply.MakeMultiBulkReply(p.cmdLine).ToBytes()
		_, err := handler.aofFile.Write(data)
		if err != nil {
			logger.Warn(err)
		}
	}
}

到此这篇关于Redis内存数据库示例分析的文章就介绍到这了,更多相关Redis内存数据库内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • redis数据库查找key在内存中的位置的方法

    一.预先需要了解的知识1.redis 中的每一个数据库,都由一个 redisDb 的结构存储.其中,redisDb.id 存储着 redis 数据库以整数表示的号码.redisDb.dict 存储着该库所有的键值对数据.redisDb.expires 保存着每一个键的过期时间.2.当redis 服务器初始化时,会预先分配 16 个数据库(该数量可以通过配置文件配置),所有数据库保存到结构 redisServer 的一个成员 redisServer.db 数组中.当我们选择数据库 select n

  • 内存型数据库Redis持久化小结

    因为Redis是内存型数据库,所以为了防止因为系统崩溃等原因导致数据丢失的问题,Redis提供了两种不同的持久化方法来将数据存储在硬盘里面,一种方法是快照(RDB),它可以将存在于某一个时刻的所有数据都写入到硬盘里面,另外一种方法是只追加文件(AOF),它会在执行写命令时,将被执行的写命令都写入到硬盘里面. 快照持久化 Redis可以通过创建快照来获得在内存里面的数据在某一个时间点上的副本.在创建快照之后,用户可以对快照进行备份,可以将快照复制到其它服务器从而创建具有相同数据的服务器副本,还可以

  • Redis内存数据库示例分析

    目录 redies dict字典 Redis的DB实现 具体的实现器 Redis持久化Aof redies dict字典 这是 Redis 最底层的结构,比如 1个DB 下面有 16个Dict 1. 使用接口方式, 基础实现是simple_dict ,sync_dict ,后续用户可以根据自己的需求进行自定义的实现属于自己的Dict , 在 forEach 方法 支持匿名函数方式 type Dict interface { Get(key string) (val interface {}, e

  • Spring Boot示例分析讲解自动化装配机制核心注解

    目录 1. 自动化装配介绍 2. Spring Boot 自动化配置UML图解 3. Spring Boot 自动化配置核心注解分析 3.1 @Inherited 3.2 @SpringBootConfiguration 3.3 @EnableAutoConfiguration 3.4 @ComponentScan 3.5 @ConfigurationPropertiesScan 3.6 @AutoConfigurationImportSelector 3.7 @AutoConfiguratio

  • 在Python中使用AOP实现Redis缓存示例

    越来越觉得的缓存是计算机科学里最NB的发明(没有之一),本文就来介绍了一下在Python中使用AOP实现Redis缓存示例,小伙伴们一起来了解一下 import redis enable=True #enable=False def readRedis(key): if enable: r = redis.Redis(host='10.224.38.31', port=8690,db=0, password='xxxx') val = r.get(key) if val is None: pri

  • javascript递归函数定义和用法示例分析

    递归函数:是指函数直接或间接调用函数本身,则称该函数为递归函数. 这句话理解起来并不难,从概念上出发,给出以下的例子: function foo(){ console.log("函数 foo 是递归函数."); foo(); } 这个例子的 foo 函数就是一个递归函数. 当你把这个函数拿到浏览器上运行的时候,你会发现内存溢出了,为什么呢?因为这个递归函数没有停止处理或运算的出口,因此这个递归函数就演变为一个死循环. 那如何使用递归呢? 使用递归函数必须要符合两个条件: 1. 在每一次

  • Java中时间戳的获取和转换的示例分析

    日期时间是Java一个重要的数据类型,常见的日期时间格式通常为"yyyy-MM-dd HH:mm:ss",但是计算机中存储的日期时间并非字符串形式,而是长整型的时间戳.因为字符串又占用空间又难以运算,相比之下,长整型只占用四个字节,普通的加减乘除运算更是不在话下,所以时间戳是最佳的日期时间存储方案. 获取时间戳的代码很简单,只需调用System类的currentTimeMillis方法即可,如下所示: // 从System类获取当前的时间戳 long timeFromSystem =

  • springboot整合Mybatis、JPA、Redis的示例代码

    引言 在springboot 项目中,我们是用ORM 框架来操作数据库变的非常方便.下面我们分别整合mysql ,spring data jpa 以及redis .让我们感受下快车道. 我们首先创建一个springboot 项目,创建好之后,我们来一步步的实践. 使用mybatis 引入依赖: <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-

  • SpringBoot整合Redis的示例

    redis是最常用的缓存数据库,常用于存储用户登录token.临时数据.定时相关数据等. redis是单线程的,所以redis的操作是原子性的,这样可以保证不会出现并发问题. redis基于内存,速度非常快,据测试,redis读的速度是110000次/s,写的速度是81000次/s 本节介绍SpringBoot引入redis,以及使用RedisTemplate来操作redis数据. 采用SpringBoot 2.1.9.RELEASE,对应示例代码在:https://github.com/lao

  • Python调用Redis的示例代码

    #!/usr/bin/env python # -*- coding:utf-8 -*- # ************************************* # @Time : 2019/8/12 # @Author : Zhang Fan # @Desc : Library # @File : MyRedis.py # @Update : 2019/8/23 # ************************************* import redis class MyR

  • c#使用csredis操作redis的示例

    现在流行的redis连接客户端有StackExchange.Redis和ServiceStack.Redis,为什么选择csredis而不是这两个? .net 最有名望的 ServiceStack.Redis 早已沦为商业用途,在 .NETCore 中使用只能充值: 后来居上的 StackExchange.Redis 虽然能用,但线上各种 Timeout 错误把人坑到没脾气,两年多两年多两年多都不解决,最近发布的 2.0 版本不知道是否彻底解决了底层. csredis支持.net40/.net4

  • 解析高可用Redis服务架构分析与搭建方案

    基于内存的Redis应该是目前各种web开发业务中最为常用的key-value数据库了,我们经常在业务中用其存储用户登陆态(Session存储),加速一些热数据的查询(相比较mysql而言,速度有数量级的提升),做简单的消息队列(LPUSH和BRPOP).订阅发布(PUB/SUB)系统等等.规模比较大的互联网公司,一般都会有专门的团队,将Redis存储以基础服务的形式提供给各个业务调用. 不过任何一个基础服务的提供方,都会被调用方问起的一个问题是:你的服务是否具有高可用性?最好不要因为你的服务经

随机推荐