存储系统

415 阅读7分钟

主要任务

需要掌握的知识

DAY1:

  1. 在main.go中写了一个简单键值对增删改查
/*写在前头
1.这是用go实现简单的K-V存储,包括了简单的增删改查
*/
package main // 声明 main 包,表明当前是一个可执行程序

import (
	"bufio"
	"fmt"
	"os"
	"strings"
) // 导入内置 fmt 包

type myElement struct { //新建 我的元素 类 里面包括名字 surname 跟唯一的id值
	Name    string
	SurName string
	Id      string
}

//用原生的go map来实现K-V春初,map变量被申明为全局变量
var DATA = make(map[string]myElement) //键值对 string - myElement

/***********增删该查函数******************/
func ADD(k string, n myElement) bool { //返回是否成功
	if k == "" {
		return false
	}

	if LOOKUP(k) == nil { //如果已存在 就是白
		DATA[k] = n
		return true
	}
	return false
}

func DELETE(k string) bool {
	if LOOKUP(k) != nil { //不存在就失败
		delete(DATA, k)
		return true
	}
	return false
}

func LOOKUP(k string) *myElement {
	_, ok := DATA[k]//根据所给键查找 对应map,如果有就返回地址对应的值,否则返回nil
	if ok {
		n := DATA[k]//获得对应的键值对地址,返回 
		return &n
	} else {
		return nil
	}
}

func CHANGE(k string, n myElement) bool {
	DATA[k] = n
	return true
}

func PRINT() {
	for k, v := range DATA {//打印整个data
		fmt.Printf("key: %s value: %v", k, v)
	}
}

func main() {//io流
	scanner := bufio.NewScanner(os.Stdin)
	for scanner.Scan() {
		text := scanner.Text()//获取当前输入的字符串
		text = strings.TrimSpace(text)//前后清除空格
		tokens := strings.Fields(text)//返回切片

		switch len(tokens) {
		case 0://如果长度为0,代表没东西
			continue
		case 1://无论输入的是多少个,最终拼接为5个长
			tokens = append(tokens, "")
			tokens = append(tokens, "")
			tokens = append(tokens, "")
			tokens = append(tokens, "")
		case 2:
			tokens = append(tokens, "")
			tokens = append(tokens, "")
			tokens = append(tokens, "")
		case 3:
			tokens = append(tokens, "")
			tokens = append(tokens, "")
		case 4:
			tokens = append(tokens, "")
		}
		switch tokens[0] {//判断是要干嘛
		case "PRINT"://打印全部
			PRINT()
		case "STOP"://停止 推出
			return
		case "DELETE":
			if !DELETE(tokens[1]) {
				fmt.Println("Delete operations failed")
			}
		case "ADD":
			n := myElement{tokens[2], tokens[3], tokens[4]}
			if !ADD(tokens[1], n) {
				fmt.Println("Add operation failed")
			}
		case "LOOKUP":
			n := LOOKUP(tokens[1])
			if n != nil {
				fmt.Printf("%v\n", n)
			}

		case "CHANGE":
			n := myElement{tokens[2], tokens[3], tokens[4]}
			if !CHANGE(tokens[1], n) {
				fmt.Println("Update operation failed")
			}

		default:
			fmt.Println("Unknown command - please try again!")

		}
	}
}

  1. 安装gin并且 www.cnblogs.com/tudaogaoyan… 原型作为框架
  2. 下载redis 与mogodb
  3. 安装mogodb 并且让go链接他
package main

//go链接数据库

import (
	"fmt"
	"log"

	"gopkg.in/mgo.v2"
	"gopkg.in/mgo.v2/bson"
)

type Person struct {
	Id         int
	First_name string
}

func main() {
	session, err := mgo.Dial("localhost:27017")
	if err != nil {
		panic(err)
	}
	defer session.Close()

	// Optional. Switch the session to a monotonic behavior.
	session.SetMode(mgo.Monotonic, true)

	c := session.DB("test").C("people")
	err = c.Insert(&Person{11, "13478808311"},
		&Person{13, "15040268074"})
	if err != nil {
		log.Fatal(err)
	}

	var persons []Person
	err = c.Find(bson.M{}).All(&persons) //把数据丢入persons里
	fmt.Println(persons)                 //看记录长啥样子

	// fmt.Println("Name:", result.Id)
	// fmt.Println("Phone:", result.First_name)
}

  1. 学习gin www.cnblogs.com/-beyond/p/9… 入门知识
  2. 学习mogodb基本语句 www.runoob.com/mongodb/mon…

Day2 1.

页面简单回显数据

  1. restful风格的增删改查,链接数据库直接进行操作

什么是restful风格 跟以往的区别 www.jianshu.com/p/7893169a7… www.jianshu.com/p/a2f067cd4… 如何做

1. 控制器
创建 controllers 文件夹和对应的文件 movies.go
movies.go
2.路由
创建一个 routes文件夹,并创建对应的文件 routes.go
routes.go
3. Models
创建 models 文件夹和对应的文件 db.go(数据层),封装对MongoDB的封装
4.业务逻辑层 models/movies.go 

前端与restful两种风格

3. 链接git

链接时出现的问题 www.cnblogs.com/fengluzhewe… blog.csdn.net/qq_38402659… 必看!!! blog.csdn.net/qq_40143332… 操作

  1. 试着整合redis

redis作为二级缓存 只读不写,在代码中试着改写该功能 blog.csdn.net/weixin_4269… 参考 redis 与mogondb的整合test

package main

import (
	"fmt"

	//此处注意“_”表示引用mysql函数中init的方法而无需使用函数
	"github.com/garyburd/redigo/redis"
	"gopkg.in/mgo.v2"
	"gopkg.in/mgo.v2/bson"
)

type Person struct {
	Id         string `json:"id" form:"id"`
	First_name string `json:"first_name" form:"first_name"`
	// LastName  string `json:"last_name" form:"last_name"`
}

func main() {
	var cmd string

	for {
		fmt.Println("请输入命令:")
		fmt.Scan(&cmd)
		//fmt.Println("你输入的是:",cmd)

		switch cmd { //如果命令是getall
		case "getall":
			GetAll()
		default:
			fmt.Println("不能识别的命令")
		}

		fmt.Println()
	}

}

func GetAll() {
	//先看看redis里有没有数据
	conn, _ := redis.Dial("tcp", "localhost:6379") //链接redis库
	defer conn.Close()
	reply, err := conn.Do("lrange", "mlist", 0, -1) //获取所有值,
	pkeys, _ := redis.Strings(reply, err)           //把值仍入pkeys中
	fmt.Println(pkeys)

	if len(pkeys) > 0 {
		//如果有
		fmt.Println("从redis获得数据")

		// 从redis里直接读取
		for _, key := range pkeys {
			retStrs, _ := redis.Strings(conn.Do("hgetall", key))
			//fmt.Println(retStrs)
			fmt.Printf("{%s}\n", retStrs[1])
		}

	} else {
		//如果没有
		fmt.Println("从mongodb获得数据")

		session, err := mgo.Dial("localhost:27017") //链接mongodb
		if err != nil {
			panic(err)
		}
		defer session.Close()

		// Optional. Switch the session to a monotonic behavior.
		session.SetMode(mgo.Monotonic, true)

		c := session.DB("test").C("people")


		var persons []Person
		err = c.Find(bson.M{}).All(&persons) //把数据丢入persons里
		fmt.Println(persons)                 //看记录长啥样子


		//写入redis并且设置过期时间
		for _, p := range persons {
			//将p以hash形式写入redis
			_, e1 := conn.Do("hmset", p.Id, "name", p.First_name)

			//将这个hash的key加入mlist
			_, e2 := conn.Do("rpush", "mlist", p.Id)

			//设置过期时间
			_, e3 := conn.Do("expire", p.Id, 60) //60s就过期
			_, e4 := conn.Do("expire", "mlist", 60)

			if e1 != nil || e2 != nil || e3 != nil || e4 != nil {
				fmt.Println(p.First_name, "写入失败", e1, e2, e3, e4) //上面哪一步随意一部错就这样
			} else {
				fmt.Println(p.First_name, "写入成功")
			}
		}
	}

}
    
  1. 配置文件yaml学习

blog.csdn.net/yuyinghua03… yaml读取 www.cnblogs.com/zhaof/p/895… 具体使用

配置文件需要应对的几种场景

  • IDE运行/调试时期读取配置文件
  • 运行单测或者benchmark Test的时候读取配置文件
  • 可执行文件(部署文件)读取配置文件
package database

//go链接数据库,数据层

import (
	"database/sql"
	"fmt"
	"io/ioutil"

	"gopkg.in/mgo.v2"
	"gopkg.in/yaml.v2"
)

type Mongodbyaml struct {
	Host   string `yaml:"host"`
	User   string `yaml:"user"`
	Pwd    string `yaml:"pwd"`
	Dbname string `yaml:"dbname"`
	Cname  string `yaml:"cname"`
}

type Config struct {
	MongodbyamlConfig Mongodbyaml `yaml:"Mongodbyaml"`
}

func (c *Config) getConf() *Config {
	yamlFile, err := ioutil.ReadFile("conf.yaml") // // yaml解析的时候c.data如果没有被初始化,会自动为你做初始化
	if err != nil {
		fmt.Println(err.Error())
	}
	err = yaml.Unmarshal(yamlFile, c) //把值装进c中
	if err != nil {
		fmt.Println(err.Error())
	}
	return c
}

var SqlDB *sql.DB

func init() {
	var c Config        //c是conf类 与配置文件的相同,并且在同一目录下
	conf := c.getConf() //方法 获取配置文件

	SqlDB, err := mgo.Dial(conf.MongodbyamlConfig.Host) //连接 正确返回session否则返回err
	if err != nil {
		panic(err) //如果确实有错 则报出来
	}
	defer SqlDB.Close() //关闭

	// Optional. Switch the session to a monotonic behavior.
	SqlDB.SetMode(mgo.Monotonic, true) //设置读写模式
}

func ConnecToDB() *mgo.Session { //连接数据库返回给models

	var c Config        //c是conf类 与配置文件的相同,并且在同一目录下
	conf := c.getConf() //方法 获取配置文件

	session, err := mgo.Dial(conf.MongodbyamlConfig.Host)
	if err != nil {
		panic(err)
	}
	//defer session.Close()
	session.SetMode(mgo.Monotonic, true)
	return session
}

4.openresty反向代理学习

www.jianshu.com/p/33d4a3fdc… nginx在go中 blog.csdn.net/weixin_3811… 反向代理的作用 www.jianshu.com/p/0b6f80949…

	server {
        listen       8081;
        server_name  localhost;

        location / {
            proxy_pass http://localhost:8080/;
        }
    }

5.压力测试ab

www.cnblogs.com/crazycoderl… windows 下载安装 blog.csdn.net/qq_26525215… 具体意思 www.jianshu.com/p/a22174de2… 必看

blog.csdn.net/tengxing007…

先让项目跑起来,cmd 进入bin 目录下 开启服务 调用命令

6.利用prrof和火焰图分析瓶颈

www.jb51.net/article/144… 必看

在router.go中加入这一行

	err := http.ListenAndServe(":9909", nil)//pprof 火焰图分析瓶颈
	if err != nil {
		panic(err)
	}

www.cnblogs.com/onemorepoin… 安装graphviz blog.csdn.net/zhenghexcs/… 画图 blog.csdn.net/wangdenghui… 必看!!!! www.jianshu.com/p/a22174de2…blog.csdn.net/weixin_3440… 怎么调优? **
**


7. 通过配置文件与接口实现 三种存储引擎切换

www.cnblogs.com/Luck1996/p/… 参考这个

8.redis 实现

redis 实现分页

www.cnblogs.com/ricklz/p/95… redis 实现语法

godis 基本格式

9.map 与rabbiemq

blog.51cto.com/13447608/24… 消息队列与go blog.csdn.net/weixin_3973… 安装 blog.51cto.com/13447608/24… 试例 www.cnblogs.com/chaselogs/p… 实例2 开两个窗口发消息(提供方与消费方) blog.csdn.net/zhghost/art… 必看!!!!! www.jianshu.com/p/ab64681be… 为什么要主从 blog.csdn.net/zhghost/art… 不同路由!!!

package models

import (
	"fmt"
	"log"
	"strings"

	"github.com/streadway/amqp"
)

const MQURL = "amqp://guest:guest@127.0.0.1:5672/"

//创建rabbitmq结构体实例
type RabbitMQ struct {
	conn      *amqp.Connection
	channel   *amqp.Channel
	QueueName string
	Exchange  string
	Key       string
	Mqurl     string
}

func NewRabbitMQ(queueName string, Exchange string, key string) *RabbitMQ {
	rabbitmq := &RabbitMQ{QueueName: queueName, Exchange: Exchange, Key: key, Mqurl: MQURL}
	var err error
	rabbitmq.conn, err = amqp.Dial(rabbitmq.Mqurl)
	rabbitmq.failOnErr(err, "创建连接错误")
	rabbitmq.channel, err = rabbitmq.conn.Channel()
	rabbitmq.failOnErr(err, "获取channel失败")
	return rabbitmq
}

//断开channel和connection
func (r *RabbitMQ) Destroy() {
	r.channel.Close()
	r.conn.Close()
}

//错误处理函数
func (r *RabbitMQ) failOnErr(err error, message string) {
	if err != nil {
		log.Fatalf("%s:%s", message, err)
		panic(fmt.Sprintf("%s%s", message, err))
	}
}

//simple模式step1: rabbitmq的实例
func NewRabbitMQSimple(queueName string) *RabbitMQ {
	return NewRabbitMQ(queueName, "", "")
}

//简单模式step:2.简单模式下生产代码
func (r *RabbitMQ) PublishSimple(message string) {
	//申请队列,如果队列不存在会自动创建,如果存在则跳过创建
	//保证队列存在,消息能发送到队列中
	_, err := r.channel.QueueDeclare(
		r.QueueName,
		//是否持久化
		false,
		//是否为自动删除
		false,
		//是否具有排他性
		false,
		//是否阻塞
		false,
		//额外属性
		nil,
	)
	if err != nil {
		fmt.Println(err)
	}
	//发送消息到队列中
	err = r.channel.Publish(
		r.Exchange,
		r.QueueName,
		//如果为true,根据exchange类型和routkey规则,如果无法找到符合条件的队列那么会把发送的消息返回给发送者
		false,
		false,
		amqp.Publishing{
			ContentType: "text/plain",
			Body:        []byte(message),
		})
	if err != nil {
		fmt.Println(err)
	}
}

func (r *RabbitMQ) ConsumeSimple() {
	_, err := r.channel.QueueDeclare(
		r.QueueName,
		//是否持久化
		false,
		//是否为自动删除
		false,
		//是否具有排他性
		false,
		//是否阻塞
		false,
		//额外属性
		nil,
	)
	if err != nil {
		fmt.Println(err)
	}
	msgs, err := r.channel.Consume(
		r.QueueName,
		//用来区分多个消费者
		"",
		//是否自动应答
		true,
		//是否具有排他性
		false,
		//如果设置为true,表示不能将同一个connection中发送的消息传递给这个connection中的消费者
		false,
		//队列消费是否阻塞
		false,
		//其他属性
		nil,
	)
	if err != nil {
		fmt.Println(err)
	}
	forever := make(chan bool)
	//启用协程处理消息
	go func() {
		for d := range msgs {
			//实现我们要处理的逻辑函数
			log.Printf("Received a message:%s")
			fmt.Println(string(d.Body))
			stringbody := string(d.Body)
			b := strings.Split(stringbody, "///")
			if b[0] == "add" { //如果表示新增,就往从库中加
				DATA2[b[1]] = b[2]
			} else if b[0] == "edit" {
				DATA2[b[1]] = b[2]
			} else if b[0] == "delete" {
				delete(DATA2, b[1]) // 删除不存在的key,原m不影响
			}
			fmt.Println(DATA2)
		}
	}()

	<-forever
}

  1. 画设计类图
  2. 单元测试

看覆盖率资料!

红色为未覆盖 绿色的则是已覆盖

参考博客;github.com/guyan0319/g…

  1. 接口文档 www.showdoc.cc/CURD?page_i…