ch3-使用MongoDB数据持久化--使用go-gin创建分布式应用
系列文章目录
第一章 gin初步认识
第二章 设置API
第三章 使用MongoDB数据持久化
注:
- 系列文章是对应上述英文原版书的学习笔记
- 相关自己的练习代码包含注释,放在在本人的gitee,欢迎star
- 所有内容允许转载,如果侵犯书籍的著作权益,请联系删除
- 笔记持续更新中
使用MongoDB数据持久化
前言
本章将会用docker部署MongoDB和Redis,实现CRUD,介绍标准go项目目录结构,优化API响应提高网站性能
go使用mongodb
- 在项目中获取依赖
go get go.mongodb.org/mongo-driver/mongo
这将会下载驱动到系统GOPath,并把其作为依赖写入go.mod文件中
- 连接MongoDB
- docker运行mongodb
docker run -d --name mongodb -e MONGO_INITDB_ROOT_ USERNAME=admin -e MONGO_INITDB_ROOT_PASSWORD=password -p 27017:27017 mongo:4.4.3
- 使用免费的mongo atlas数据库
- 数据库的连接和测试代码
package main
import (
"context"
"fmt"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"go.mongodb.org/mongo-driver/mongo/readpref"
"log"
)
var ctx context.Context
var err error
var client *mongo.Client
// 使用环境变量定义的数据库地址
//var uri = os.Getenv("MONGO_URI")
var uri = "***********************************************************"
func init() {
ctx = context.Background()
client, err = mongo.Connect(ctx, options.Client().ApplyURI(uri))
if err = client.Ping(context.TODO(), readpref.Primary()); err != nil {
log.Fatal(err)
}
fmt.Println("Connected to MongoDB")
}
func main() {
}
- 使用上一章的数据初始化数据库
func init() {
recipes = make([]model.Recipe, 0)
// 读取文件中的信息
file, _ := ioutil.ReadFile("recipes.json")
// 把信息解析为recipe实体
_ = json.Unmarshal([]byte(file), &recipes)
...
// InsertMany传递的数据参数就是interface
var listOfRecipes []interface{}
for _, recipe := range recipes {
listOfRecipes = append(listOfRecipes, recipe)
}
collection := client.Database(database_name).Collection(collection_name)
insertManyResult, err := collection.InsertMany(ctx, listOfRecipes)
if err != nil {
log.Fatal(err)
}
log.Println("Inserted recipes: ", len(insertManyResult.InsertedIDs))
}
MongoDB在插入数据的时候,只要没创建,就会默认创建指定数据模型的库(在MongoDB中库叫做collection 集合,插入的记录叫做document 文档)
collection.InsertMany
接收interface{}
切片类型的数据,所以上面把recipes数组的数据循环拷贝到listOfRecipes的interface切片中
recipes.json文件有如下内容
[
{
"id": "c80e1msc3g21dn3s62e0",
"name": "Homemade Pizza",
"tags": [
"italian",
"pizza",
"dinner"
],
"ingredients": [
"1 1/2 cups (355 ml) warm water (105°F-115°F)",
"1 package (2 1/4 teaspoons) of active dry yeast",
"3 3/4 cups (490 g) bread flour",
"feta cheese, firm mozzarella cheese, grated"
],
"instructions": [
"Step 1.",
"Step 2.",
"Step 3."
],
"PublishedAt": "2022-02-07T17:05:31.9985752 08:00"
}
]
使用mongoimport导入序列化的数据(json文件),同时初始化表(collection)
mongoimport --username admin --password password --authenticationDatabase admin --db demo --collection recipes --file recipes.json --jsonArray
CRUD操作实例
查找
// 操作数据库的collection
collection = client.Database(database_name).Collection(collection_name)
func ListRecipesHandler(c *gin.Context) {
// 获得操作数据库的游标
// cur其实是文档流
cur, err := collection.Find(ctx, bson.M{})
if err != nil {
c.JSON(http.StatusInternalServerError,
gin.H{"error": err.Error()})
return
}
defer cur.Close(ctx)
recipes := make([]model.Recipe, 0)
for cur.Next(ctx) {
var recipe model.Recipe
// 将查询到的文档装配为Recipe结构体的实体
cur.Decode(&recipe)
recipes = append(recipes, recipe)
}
c.JSON(http.StatusOK, recipes)
}
插入一条记录
func NewRecipesHandler(c *gin.Context) {
var recipe model.Recipe
// 获取并解析POST请求消息体传递过来的数据
if err := c.ShouldBindJSON(&recipe); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": err.Error()})
return
}
recipe.ID = primitive.NewObjectID()
recipe.PublishedAt = time.Now()
_, err := collection.InsertOne(ctx, recipe)
if err != nil {
fmt.Println(err)
c.JSON(http.StatusInternalServerError, gin.H{
"error": "Error while inserting a new recipe"})
return
}
c.JSON(http.StatusOK, recipe)
}
修改ID
字段的类型为primitive.Object
,并为结构体的字段加上bson
注解
// swagger: parameters recipes newRecipe
type Recipe struct {
// swagger:ignore
ID primitive.ObjectID `json:"id" bson:"_id"`
Name string `json:"name" bson:"name"`
Tags []string `json:"tags" bson:"tags"`
Ingredients []string `json:"ingredients" bson:"ingredients"`
Instructions []string `json:"instructions" bson:"instructions"`
PublishedAt time.Time `json:"PublishedAt" bson:"publishedAt"`
}
更新一条记录
func UpdateRecipeHandler(c *gin.Context) {
// 从上下文获得url传递的参数 host/recipes/{id}
// 属于位置参数
id := c.Param("id")
var recipe model.Recipe
// 从body获取数据后,
if err := c.ShouldBindJSON(&recipe); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": err.Error()})
return
}
// 从ID string新建ObjectID实体
objectId, _ := primitive.ObjectIDFromHex(id)
_, err = collection.UpdateOne(ctx, bson.M{
"_id": objectId}, bson.D{{"$set", bson.D{
{"name", recipe.Name},
{"instructions", recipe.Instructions},
{"ingredients", recipe.Ingredients},
{"tags", recipe.Tags}}}})
if err != nil {
fmt.Println(err)
c.JSON(http.StatusInternalServerError, gin.H{
"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "Recipes has been updated"})
}
设计项目的分层结构
项目分目录和分文件便于管理,让代码清晰容易
models
目录的recipe.go
定义数据模型handlers
目录的handler.go
定义路由处理函数
handler.go设计
- 把handler函数需要的上下文和数据库连接作为结构体
type RecipesHandler struct {
collection *mongo.Collection
ctx context.Context
}
// 获取handler处理需要的数据实体--上下文和数据库连接
func NewRecipesHandler(ctx context.Context, collection *mongo.Collection) *RecipesHandler {
return &RecipesHandler{
collection: collection,
ctx: ctx,
}
}
- 为
RecipesHandler
添加方法
func (handler *RecipesHandler) ListRecipesHandler(c *gin.Context) {}
main.go
存放数据库认证和链接的代码
func init() {
ctx := context.Background()
client, err := mongo.Connect(ctx, options.Client().ApplyURI(uri))
if err = client.Ping(context.TODO(), readpref.Primary()); err != nil {
log.Fatal(err)
}
log.Println("Connected to MongoDB")
// 操作数据库的collection
collection := client.Database(database_name).Collection(collection_name)
recipesHandler = handlers.NewRecipesHandler(ctx, collection)
}
当前项目目录结构
MONGO_URI="mongodb://admin:password@localhost:27017/
test?authSource=admin" MONGO_DATABASE=demo go run *.go
用命令行传递变量参数,然后在程序中获取
var uri = os.Getenv("MONGO_URI")
使用redis缓存API
下面讲解怎么添加redis缓存机制到API
一般在程序运行起来后,经常查询的数据只占数据库全部数据的少部分。将获取的数据缓存到如redis这种内存缓存数据库中,可以避免每次都请求数据库调用,极大地降低数据库查询的负载,提高查询。另一方面,redis是内存数据库,比磁盘数据库的调用速度更快,有个小的系统开销。
查询的情况
- 查询缓存,得到数据,
Cache hit
- 查询缓存没有数据,
Cache miss
请求数据库数据 - 返回数据给客户端,并将其缓存到本地cache
docker运行redis
docker run -d --name redis -p 6379:6379 redis:6.0
查看日志
docker logs -f <容器ID>
编辑redis.conf定义数据置换算法
使用LRU算法 最近最少使用算法
maxmemory-policy allkeys-lru
maxmemory 512mb
为了把配置文件映射到本地,将启动docker容器命令改为
docker run -v D:/redis/conf/redis.conf:/usr/local/etc/redis/redis.conf --name redis_1 -p 6379:6379 redis:6.0 redis-server /usr/local/etc/redis/redis.conf
配置redis
import "github.com/go-redis/redis"
- 在
main.go
的init()函数里面配置redis
redisClient := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "",
DB: 0,
})
status := redisClient.Ping()
fmt.Println(status)
- 修改
handler.go
type RecipesHandler struct {
collection *mongo.Collection
ctx context.Context
redisClient *redis.Client
}
// 获取handler处理需要的数据实体--上下文和数据库连接
func NewRecipesHandler(ctx context.Context, collection *mongo.Collection, redisClient *redis.Client) *RecipesHandler {
return &RecipesHandler{
collection: collection,
ctx: ctx,
redisClient: redisClient,
}
}
使用redis对数据缓存的代码
func (handler *RecipesHandler) ListRecipesHandler(c *gin.Context) {
var recipes []models.Recipe
val, err := handler.redisClient.Get("recipes").Result()
// 如果抛出的错误是redis没有这个数据
if err == redis.Nil {
log.Printf("Request to MongoDB")
// 获得操作数据库的游标
// cur其实是文档流
cur, err := handler.collection.Find(handler.ctx, bson.M{})
if err != nil {
c.JSON(http.StatusInternalServerError,
gin.H{"error": err.Error()})
return
}
defer cur.Close(handler.ctx)
recipes = make([]models.Recipe, 0)
for cur.Next(handler.ctx) {
var recipe models.Recipe
// 将查询到的文档装配为Recipe结构体的实体
cur.Decode(&recipe)
recipes = append(recipes, recipe)
}
// 把新查到的数据已键值对的方式存入redis
data, _ := json.Marshal(recipes)
handler.redisClient.Set("recipes", string(data), 0)
} else if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"error": err.Error()})
} else {
log.Printf("Request to Redis")
recipes = make([]models.Recipe, 0)
json.Unmarshal([]byte(val), &recipes)
}
c.JSON(http.StatusOK, recipes)
}
直接查看redis缓存是否存在
- 打开redis-cli
- EXISTS recipes
使用web工具查看redis缓存
docker run -d --name redisinsight --link redis_1 -p 8001:8001 redislabs/redisinsight
网站性能跑分
apache2-utils中的软件ab用于网站压力测试(windows版本是Apache httpd这个软件)
ab.exe -n 2000 -c 100 -g without-cache.data http://localhost:8080/recipes
2000次请求,100每次的并发
将得到如下的结果:
关闭缓存-g without-cache.data
打开使用redis缓存-g with-cache.data
关注Time taken for tests
(完成所有测试的用时)和Time per request
(测试的时间平均数)
这篇好文章是转载于:学新通技术网
- 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
- 本站站名: 学新通技术网
- 本文地址: /boutique/detail/tanhgkcebh
-
photoshop保存的图片太大微信发不了怎么办
PHP中文网 06-15 -
《学习通》视频自动暂停处理方法
HelloWorld317 07-05 -
word里面弄一个表格后上面的标题会跑到下面怎么办
PHP中文网 06-20 -
Android 11 保存文件到外部存储,并分享文件
Luke 10-12 -
photoshop扩展功能面板显示灰色怎么办
PHP中文网 06-14 -
微信公众号没有声音提示怎么办
PHP中文网 03-31 -
excel下划线不显示怎么办
PHP中文网 06-23 -
excel打印预览压线压字怎么办
PHP中文网 06-22 -
TikTok加速器哪个好免费的TK加速器推荐
TK小达人 10-01 -
怎样阻止微信小程序自动打开
PHP中文网 06-13