一步两步是魔鬼的步伐

Duan1v's Blog

记录下学习使用kratos的过程

学习源

步骤及总结

新建微服务模块、版本模块:

1
2
kratos new user
kratos proto add api/user/v1/user.proto

编写proto文件,记录下简单的使用,详情见官网

  • message 定义结构体,其中的字段表示结构体的字段
  • 先定义远程方法中的接收结构体以及响应结构体
  • 再使用service 定义微服务接口,以及待实现的远程方法

输出微服务的go文件

1
make api
  • grpc.go中是定义了客户端的,可以把这个proto文件发到任意客户端,用protoc生成指定的语言,比如带上参数 –cpp_out生成cpp文件,可以明确知道这个微服务定义了哪些方法
  • protoc的具体命令在Makefile中
  • PS:输出proto文件中的rpc部分,需要带上参数参考
1
protoc --go_out=plugins=grpc:. *.proto

修改service/user/internal/conf/conf.proto,字段需与configs下面的配置保持一致

  • 为什么是configs?这是main.go在调用的时候传的参数(写在init()中的)
  • 先看下conf.proto,一个Bootstrap结构体中包含了两个字段,一个server,一个data,分别对应上层目录中相邻的模块目录,data模块主要是负责数据库的建立,以及数据表模型的CRUD;server则是决定rpc的连接方式。先这样,生成下config的go文件
1
make config
  • 然后更改下configs中的配置

现在该写下data层了

  • data.go负责数据库连接生成
  • user.go中定义数据表结构体,主要负责数据库层面的CRUD。定义字段类型时,要注意与user.proto生成中的类型是否一致,如果不一致的话,需要在返回类型中转换成user.proto中定义的类型,比如birthday字段

再看看biz层

  • 这一层是业务逻辑层
  • 根据data层返回的数据,进行业务整理
  • 可以看出biz是依赖于data的

再看看server层

  • grpc.go中定义了一个生成grpc服务的方法,关键参数是UserService,这指定了这次微服务的 主角

那就看看service层,UserService

  • 这个接口是要实现由user.proto生成的v1.UserServer接口
  • 需要继承v1.UnimplementedUserServer,这是一个针对客户端调取不存在的远程方法的错误处理
  • 也是因为go是鸭子语言,在编写UserService时,对实现接口的方法不敏感,可能写的方法名与v1.UserServer接口定义的不一致;当然它的好处是,不妨碍调用已实现的方法
  • 这个UserService是依赖于biz层的,主要是负责通过biz层处理结果整理相应远程函数的返回数据格式

那么这些层是怎么串起来的呢

  • 先整理下依赖关系

server->service->biz->data

  • 当然可以手动生成各部分依赖,不过有更简单地方式:wire

  • 在各层中,server.go、service.go、biz.go、data.go中都将各自的逻辑放到了provider中

1
var  ProviderSet = wire.NewSet(NewUserUsecase)
  • 可以在cmd/user中看到wire.go(这个文件是不经过IDE校验的——无法ctl+鼠标点击到相应方法,这是由于前两行),通过在该文件夹下执行 wire 命令,可以自动生成wire_gen.go

  • 在wire.go中,wire.build参数不仅有上面的各个ProviderSet,还有一个main.go中的函数newApp(),然后发起了一次依赖的自行实例化,返回一个kratos.App指针

  • 入口文件main.go中就是获取配置,使kratos.App跑起来

1
kratos run

测试相关

ginkgo 文档

  • 先在需要测试的目录中,编写*_suit_test.go,比如:data层目录;在BeforeSuite方法中连接数据库,在AfterSuite可以打印一些结束标志吧;然后再user_test.go中编写user.go的相关测试用例

  • 使用gomock,需要对指定接口生成mock文件,参考;这里需要注意下**“user/internal/biz”**,这个需要写完整,而不能写 .

1
mockgen -destination=../mock/mrepo/user.go -package=mrepo "user/internal/biz" UserRepo
  • 为了方便,可以在UserRepo接口上添加这句,方法有变动,方便重新生成
1
//go:generate mockgen -destination=../mock/mrepo/user.go -package=mrepo "user/internal/biz" UserRepo

使用kratos封装的consul的一些坑, 官方示例

shop/configs/config.yaml ;写成 “discovery:///shop.user.service” , 会报 “endpoint found,refused to write” ;还是得改成"127.0.0.1:50052",才能调用。

wsl2搭建vscode+go+debug

wsl1不支持远程调试,必须是wsl2才可以使用

1
2
wsl -l -v
wsl --set-version Ubuntu-20.04 2

https://static.duan1v.top/images/D6opftFzNaZPVuC.png

安装dlv(略)

vscode生成测试文件,并补充完整

  • 打开需要测试的文件,f1或者shift+ctrl+p,键入test,选择如图选项

https://static.duan1v.top/images/hvwF31tAj4VrZ97.png

  • todo部分补充完整

https://static.duan1v.top/images/brA5Tp7jsYkiJ2F.png

配置

  • 生成配置文件

https://static.duan1v.top/images/TeURr51k4f3698y.png

  • 更改配置
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
{
            "name": "Connect to server",
            "type": "go",
            "request": "attach",
            "mode": "remote",
            "remotePath": "/",
            "debugAdapter": "dlv-dap",
            "port": 12345,
            "host": "127.0.0.1"
        }

https://static.duan1v.top/images/PdMsFauTEzyNWmw.png

  • 添加断点,点击debug_test 开始debug

https://static.duan1v.top/images/z5ErMwbBIhYu3UH.png

  • 手动触发

执行命令

1
-> % dlv debug --headless --listen=:12345 --log --api-version=2

点击按钮

https://static.duan1v.top/images/1obgvFEA4jVuZiK.png

wsl2使用主机代理之panda vpn

网上好多都是使用clash的的教程,设置vpn部分,panda点个✔就行

https://static.duan1v.top/images/dFSM1zBm72XCeag.png

配置一个开启代理的sh脚本:proxy.sh(来源找不到了)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#!/bin/sh
hostip=$(cat /etc/resolv.conf | grep nameserver | awk '{ print $2 }')
wslip=$(hostname -I | awk '{print $1}')
port=41091

PROXY_HTTP="http://${hostip}:${port}"

set_proxy(){
        export http_proxy="${PROXY_HTTP}"
        export HTTP_PROXY="${PROXY_HTTP}"

        export https_proxy="${PROXY_HTTP}"
        export HTTPS_proxy="${PROXY_HTTP}"
        echo "http proxy success!"
}

unset_proxy(){
        unset http_proxy
        unset HTTP_PROXY
        unset https_proxy
        unset HTTPS_PROXY
}

test_setting(){
        echo "Host ip:" ${hostip}
        echo "WSL ip:" ${wslip}
        echo "Current proxy:" $https_proxy
}

if [ "$1" = "set" ]
then
        set_proxy

elif [ "$1" = "unset" ]
then
        unset_proxy

elif [ "$1" = "test" ]
then
        test_setting
else
        echo "Unsupported arguments."
fi

开启代理

1
-> % source ~/proxy.sh set

检测是否成功

1
w3m google.com

出现下图则成功

https://static.duan1v.top/images/egEB9u3FsILlz1k.png

附 wsl 使用代理:

1
vim ~/.zshrc

追加下面代码

1
2
3
4
5
6
7
8
startProxy(){
        export https_proxy="http://127.0.0.1:41091";
        export http_proxy="http://127.0.0.1:41091";
}
stopProxy(){
        unset https_proxy;
        unset http_proxy;
}
1
2
3
source ~/.zshrc
# 开启代理
startProxy

附ping端口

1
2
3
4
sudo apt install tcptraceroute
sudo wget http://www.vdberg.org/~richard/tcpping -O /usr/bin/tcping
sudo chmod +x /usr/bin/tcping
tcping 目标IP 目标端口

MySQL学习笔记:索引失效

EXPLAIN中的type为range,而且rows过大时:

https://static.duan1v.top/images/fnPZRGhOQXNVeCx.png
MySQL学习笔记:索引失效
  • 查询临界值示例:
https://static.duan1v.top/images/gA9Bo4uTJKWNHVR.png
MySQL学习笔记:索引失效

一般情况:

以下参考:索引失效的情况(很棒的一篇文章,不过有些不准确,比如:使用 >= 不会失效,此处做个总结记录)

WHERE子句中

  • 未遵循最左原则
  • 在索引列上计算,使用函数
  • 在索引列上自动(如:`phone`=18172874277,`phone`是字符串型,所以等号后面的值应加引号),手动转换类型
  • 某列使用'>','<',右侧的索引列(按照组合索引字段顺序)
  • LIKE以通配符开头
  • 组合索引列之间使用OR

GROUP BY子句中(WHERE条件的索引是走的,之后的GROUP BY或者ORDER BY的索引是不走的)

不走索引的标准可以观察执行计划中的Extra: GROUP BY将使用额外的文件排序,以及产生临时表

  • 使用组合索引,须遵守最左原则(须按照索引顺序),WHERE子句中的索引字段是等值查询时,可以在GROUP BY子句中省略;
https://static.duan1v.top/images/LwYiUt9D7CJfanO.png
MySQL学习笔记:索引失效
https://static.duan1v.top/images/Rnzih58LpGmEbeS.png
MySQL学习笔记:索引失效
  • 存在一个组合索引和一个单索引时,GROUP BY的索引字段在WHERE子句中都有,且WHERE子句中的组合索引是等值查询,且WHERE子句中的单索引是等值查询或者执行计划中的key是单索引时,是用到索引的
https://static.duan1v.top/images/z4nIEPBYRivt9ks.png
MySQL学习笔记:索引失效
https://static.duan1v.top/images/gmuWPfkLtFHwDKb.png
MySQL学习笔记:索引失效

ORDER BY子句中,

不走索引的标准可以观察执行计划中的Extra: ORDER BY将使用额外的文件排序

  • 不使用WHERE子句时
https://static.duan1v.top/images/CXgj8Hv4cJoeySz.png
MySQL学习笔记:索引失效
  • 使用组合索引,须遵守最左原则(须按照索引顺序),且WHERE子句中有效的组合索引;WHERE子句中的有效索引字段是等值查询时,可以在ORDER BY子句中省略;
https://static.duan1v.top/images/VNpLDdQxzmtw2UW.png
MySQL学习笔记:索引失效
  • 存在多个单索引,或存在非索引字段时
https://static.duan1v.top/images/g4ZtmLA56KxSRCb.png
MySQL学习笔记:索引失效
  • 存在一个组合索引和一个单索引时,当表中只有一个组合索引,ORDER BY的索引字段在WHERE子句中都有,且WHERE子句中的组合索引是等值查询,且WHERE子句中的单索引是等值查询或者执行计划中的key是单索引时,是用到索引的
https://static.duan1v.top/images/sJZhQfiam5uDtFc.png
MySQL学习笔记:索引失效

注意:

  • 使用IN时,只有一个值时,相当于'=',有多个值时,索引也是有效的
https://static.duan1v.top/images/U9ouwYeEWIMHstP.png
MySQL学习笔记:索引失效
  • 索引列各自使用OR时,相当于IN
https://static.duan1v.top/images/N94UEZYsecyd25n.png
MySQL学习笔记:索引失效
  • LIKE以通配符结尾时,索引有效
https://static.duan1v.top/images/1lqGQ6FUvaNtIeW.png
MySQL学习笔记:索引失效
  • 使用'<=',索引有效
https://static.duan1v.top/images/uoQsRE8d3Sft1Kk.png
MySQL学习笔记:索引失效
  • 上述情况,EXPLAIN中的type为range,在rows过大时,索引均会失效。

MySQL学习笔记:组合索引-索引下推

索引下推

官网地址:Index Condition Pushdown Optimization

个人理解:

  • 开启ICP后,在查询索引时,会先检查无效索引(未走上的索引)是否满足条件,减少回表查询的次数,从而节省了查询 满足有效索引列条件下的 不符合无效索引列条件的所有行的查询时间。
https://static.duan1v.top/images/ugxmNVEaz8UcbJv.png
MySQL学习笔记:组合索引-索引下推
  • 很明显,索引没有走到uname
  • 在关闭ICP时,MySQL会回表查询`uname`!='5f686d59875ef'这个条件的完整行
  • 在开启ICP时,MySQL会先检查索引中的`uname`,然后在进行全表查询

实际查询,对比开启和关闭ICP时所用时间

1
2
3
4
5
SET profiling =  1; -- 开启profiling
SET query_cache_type =  0; -- 关闭query_cache
SET GLOBAL query_cache_size =  0;
SET optimizer_switch = 'index_condition_pushdown=off'; -- 关闭ICP
SET optimizer_switch = 'index_condition_pushdown=on'; -- 开启ICP
  • 先看看EXPLAIN,有效索引列:`dept_id`,无效索引列:`uname`
https://static.duan1v.top/images/Hbf6lrKLCOEsxGW.png
MySQL学习笔记:组合索引-索引下推
https://static.duan1v.top/images/wYDvVBx32I7LTbp.png
MySQL学习笔记:组合索引-索引下推
  • 再在开启关闭的情况下,分别执行三次SQL,查看查询时间;开启后比关闭后节省了大概75%的时间
https://static.duan1v.top/images/a3kwIErRglPn9UY.png
MySQL学习笔记:组合索引-索引下推

ICP注意事项:

  • EXPLAIN中的type须是range, ref, eq_ref, ref_or_null之一
  • 支持引擎:Myisam,InnoDB,允许分区
  • 对于InnoDB,不支持聚簇索引,只支持二级索引
  • 不支持虚拟列,子查询,存储函数,触发条件
  • 索引覆盖时,不用ICP

EXPLAIN中的type是range:

  • EXPLAIN中,type为range时,MySQL会根据符合有效索引列条件的计算rows(InnoDB是估值),如果过大,即使用索引不划算,将不走索引,从而不使用ICP
https://static.duan1v.top/images/VqpiY7aMRfk2HSU.png
MySQL学习笔记:组合索引-索引下推

索引覆盖

SELECT子句中查询的字段只有索引列,因此不需要根据二级索引查到的聚簇索引,再次回表查询完整行。

https://static.duan1v.top/images/JKR6epXSnsHN8WY.png
MySQL学习笔记:组合索引-索引下推

MySQL学习笔记:组合索引-最左原则

新建表

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '自增主键',
  `uname` varchar(100) NOT NULL DEFAULT '' COMMENT '姓名',
  `nickname` varchar(255) NOT NULL DEFAULT '' COMMENT '昵称',
  `phone` char(11) NOT NULL DEFAULT '' COMMENT '电话',
  `emp_no` varchar(20) NOT NULL DEFAULT '' COMMENT '员工编号',
  `dept_id` int(4) NOT NULL DEFAULT '0' COMMENT '部门id',
  `pwd` varchar(255) NOT NULL DEFAULT '' COMMENT '密码',
  `avt` varchar(255) NOT NULL DEFAULT '' COMMENT '头像',
  `addr` varchar(255) NOT NULL DEFAULT '' COMMENT '地址',
  PRIMARY KEY (`id`),
  KEY `index_user` (`dept_id`,`uname`,`phone`) USING BTREE,
  KEY `avt` (`avt`),
  KEY `emp_no` (`emp_no`),
  KEY `index_nickname` (`nickname`,`pwd`) USING BTREE,
  KEY `addr` (`addr`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

最左原则

  • where子句中字段的顺序不会影响索引的使用
1
2
3
4
5
6
7
8
SELECT
	*
FROM
	`user`
WHERE
	uname = '5f686bb79ff03'
AND dept_id = 91
AND phone = '14097548200';
https://static.duan1v.top/images/M3svut7faHiQZNK.png
MySQL学习笔记:组合索引-最左原则
  • where子句中,只能按照索引字段,从右向左依次截取字段,剩下的字段依次作为有效索引
1
KEY `index_user` (`dept_id`,`uname`,`phone`) USING BTREE

可以直接用的有效索引有:(dept_id,uname,phone),(dept_id,uname),(dept_id)

https://static.duan1v.top/images/HBC94xY1ODIPMmu.png
MySQL学习笔记:组合索引-最左原则

(dept_id,phone)中phone是走不上索引的,但是当满足MySQL的索引下推条件时,是可以减少回表次数,从而减少查询时间的。

https://static.duan1v.top/images/DcAJ5glGMOxupa2.png
MySQL学习笔记:组合索引-最左原则