存储与数据库新技术 | 青训营

172 阅读4分钟

新技术演进

软件架构变更 : Bypass OS kernel

SPDK:

  • Kernel Space -> User Space
    • 避免syscal带来性能的损耗,直接从用户态访问磁盘
  • 中断 -> 轮询
    • 磁盘性能提高之后,中断次数随之上升,不利于IO性能
    • SPDK poller可以绑定特定的cpu核不断轮询,减少cs,提高性能
  • 无锁数据结构
    • 使用 Lock-free queue,降低并发操作时的开销

AI增强 : 存储格式转换

通过AI技术实时分析,决定行列存储方式

新硬件革命 : 存储介质变更,计算单元变更,网络硬件变更

  • RDMA网络
    • 传统的网络协议栈,需要基于多层网络协议处理数据包,存在用户态和内核态的切换,足够通用但是性能不是最佳。
    • RDMA时kernel bypass的流派,不经过传统的网络协议栈,可以把用户态虚拟内存映射给网卡,减少拷贝开销,减少cpu开销
  • persistent memory 在NVMe SSD和main memory之间有一种全新的存储产品:persistent memory
    • IO时延介于SSD和memory之间,约百纳秒量级
    • 可以用作易失性内存,也可以用作持久化介质
  • 可编程交换机
    • P4,Switch,配有编译器,计算单元,DRAM,可以在交换机层对网络包做计算逻辑,在数据库场景下,可以实现缓存一致性协议等。
  • CPU/GPU/DPU
    • CPU:从multi-core走向many-core
    • GPU:强大的算力和越来越大的显存空间
    • DPU:异构计算,减轻CPU的workload

数据库实践 -- AI

这一技术利用人工智能来实时分析数据,并决定如何以最优的方式进行行列存储。行列存储方式在不同的数据访问模式下具有不同的优势,AI可以根据实际的数据访问模式来动态调整数据的存储格式,以提高查询性能和减少存储开销。

作业

实现一个 (分布式) key-value 存储系统

要求:

  • 基于本地文件系统实现,支持常用的put(k,v)、get(k, v)scan by_prefix(prefix)接口
  • 支持存储server独立进程部署,支持跨进程或者网络访问
  • IO操作做到低时延
  • 4.可选:支持扩展成分布式架构,多台存server组成一个分布式keyvalue存储系统,并保证全局的数据一致性

以下只完成了第一个部分。

1. 存储数据

首先,我们将使用 Go 语言来创建一个本地键-值存储系统。我们将使用 map 来保存键值对,并将数据保存到本地文件中。

goCopy code
package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"strings"
	"sync"
)

var (
	dataMutex sync.RWMutex
	data      = make(map[string]string)
)

func main() {
	// 启动 HTTP 服务器来提供接口
	// ...
}

// Put 存储键值对
func Put(key, value string) error {
	dataMutex.Lock()
	defer dataMutex.Unlock()

	data[key] = value

	// 保存数据到本地文件
	if err := saveToFile(key, value); err != nil {
		return err
	}

	return nil
}

// scanByPrefix 根据前缀扫描键值对
func scanByPrefix(prefix string) (map[string]string, error) {
	dataMutex.RLock()
	defer dataMutex.RUnlock()

	result := make(map[string]string)

	for key, value := range data {
		if strings.HasPrefix(key, prefix) {
			result[key] = value
		}
	}

	return result, nil
}

// 保存数据到本地文件
func saveToFile(key, value string) error {
	// 这里可以将数据保存到本地文件,使用 JSON 或其他格式
	// 为简化示例,这里只输出到控制台
	fmt.Printf("Saved: key=%s, value=%s\n", key, value)
	return nil
}

// 从本地文件加载数据
func loadFromFile() error {
	// 从本地文件加载数据,使用 JSON 或其他格式
	// 为简化示例,这里没有实现文件加载
	return nil
}

上述代码创建了一个简单的键-值存储系统,支持 Put 存储键值对和 scanByPrefix 根据前缀扫描键值对。数据将保存在内存中,并且每次 Put 操作都会将数据写入本地文件。

2. 启动 HTTP 服务器

为了让其他进程或网络访问这个键-值存储系统,您可以创建一个 HTTP 服务器来提供接口。以下是一个简单的示例,使用 Go 的标准库 net/http 创建一个基本的 HTTP 服务器:

goCopy code
package main

import (
	"encoding/json"
	"fmt"
	"net/http"
)

func main() {
	http.HandleFunc("/put", putHandler)
	http.HandleFunc("/scan", scanHandler)

	port := 8080
	fmt.Printf("Server is listening on port %d...\n", port)
	http.ListenAndServe(fmt.Sprintf(":%d", port), nil)
}

func putHandler(w http.ResponseWriter, r *http.Request) {
	key := r.URL.Query().Get("key")
	value := r.URL.Query().Get("value")

	if key == "" || value == "" {
		http.Error(w, "Key and value must be provided", http.StatusBadRequest)
		return
	}

	if err := Put(key, value); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	w.WriteHeader(http.StatusOK)
}

func scanHandler(w http.ResponseWriter, r *http.Request) {
	prefix := r.URL.Query().Get("prefix")

	if prefix == "" {
		http.Error(w, "Prefix must be provided", http.StatusBadRequest)
		return
	}

	result, err := scanByPrefix(prefix)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	// 返回 JSON 格式的结果
	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(result)
}

在上述代码中,我们创建了两个 HTTP 处理程序:putHandler 处理 Put 请求,scanHandler 处理 scanByPrefix 请求。您可以通过发送 HTTP 请求来与存储系统进行交互。