##前言
直接上图,借用的。
2进制10进制转换
一、基本
不允许隐式类型转换
常用集合
基本声明使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20fmt.Printf("hello word" + "\n")
// 声明字符串
var str1 = "njdkanjkdns"
fmt.Printf(str1 + "\n")
// 声明字符串2
var str2 string
str2 = "ndsjak13132"
fmt.Printf(str2 + "\n")
// 声明字符串3
var str3 string
fmt.Printf(str3 + "\n")
// 声明字符串4
str4 := "1234fdsf"
fmt.Printf(str4 + "\n")
// 声明字符串5
var (
str5 string = "dajkdnsjk11"
)
fmt.Printf(str5 + "\n")
// 标常用 var str3 string="1232"; str4:="12323";var str3,str4:="12323",'12323";变量交换
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 二进制与方式变量交换(省内存)
fmt.Println("二进制与方式变量交换(省内存))")
var a int = 100
var b int = 200
a = a ^ b
b = b ^ a
a = a ^ b
fmt.Println(a, b)
// 变量交换(常规)
fmt.Println("变量交换(常规)")
var c int = 100
var d int = 200
c, d = d, c
fmt.Println(c, d)匿名变量
1
2
3
4
5// 匿名变量 (变量不被用到时会报错 所以用_来代替)
fmt.Println("匿名变量")
e, _ := 100, 200
_, f := 100, 200
fmt.Println(e, f)常量
1
2
3
4
5
// 常量
const const1 int = 1
fmt.Println("常量")
fmt.Println("常量", const1)变量作用域
全局变量和局部变量 函数题外 var声明 全局 函数体内如果有同名局部变量 则局部变量优先数组 :
初始化固定长度1
2
3
4
5
6var tmpArr = [10]int{}
var a [3]int
a[0] = 1
b := [3]int{1,2,3}
c := [2][3]int{{1,2,4},{4,5,6}}切片:
可变长度,容量不够按照2的倍数增长容量空间,共享存储结构 (len()获取元素个数,cap获取容量)map:
相当于js 的json对象 形式可以自定义k {“asd”:1,”qwe”:2}1
2
3
4
5
6//声明
m := map[string]int{"asd":1,"qwe":2}
m1 := map[string]int{}
m1["one"] = 1
m2 := make(map[string]int,10)循环
1
2
3
4
5
6
7
8
9
10
11
12
13// 循环
fmt.Println("循环")
for i := 1; i <= 10; i++ {
fmt.Println(i)
}
// for each 循环
var tmpArr = [10]int{}
tmpArr = [10]int{2, 3, 5, 5}
tmpArr[4] = 1
for i, x := range tmpArr {
fmt.Println(i, x)
}指针
指针 1、指针存储 变量的内存地址 2、变量加&获取地址 3、指针前家* 获取指针指向的变量的值 4、修改指针变量的值,所有指向改内存的变量都会被修改1
2
3
4
5
6
7
8
9
10
11
12
13
14var ipa int = 312
var ipInt *int /* 指向整型*/
var fpFloat *float32 /* 指向浮点型 */
ipInt = &ipa
*ipInt = 23
fmt.Println(ipInt, fpFloat, ipa)
// z指针数组
var tmpArr5 = [3]int{1, 2, 3}
var fpArr [3]*int
for i := 0; i < 3; i++ {
fpArr[i] = &tmpArr5[i]
}
fmt.Println(*fpArr[0], fpArr[0])struct结构体
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22//自定义数据结构类型
type Result struct {
Code int `json:"code_json"`
Message string `json:"msg_json"`
}
type testp int
var res1 = Result{Code: 123, Message: "qwe123"}
fmt.Println(res1)
var res1Json, err = json.Marshal(res1)
fmt.Println("输出json后:", string(res1Json), err)
/*
也可以这么写
if res1Json, err = json.Marshal(res1); err != nil{
fmt.Println("输出json后报错了:", err)
}
fmt.Println("输出json后成功了:", string(res1Json))
*/
var res2 Result
err = json.Unmarshal(res1Json, &res2)
fmt.Println("json解密后:", res2, err)
管理Goroutine的三种方式
管理Goroutine的三种方式waitGroup, channel, context的区别
waitGroup
- 使用范围:
1、当一个工作需要被拆分成多个子工作,需要等待全部的子工作处理完成后才能进行下一步工作的时候,一般使用waitGroup。channel
- 使用范围:
1、用于关闭goroutine。当有一个或几个Goroutine在后台执行一些任务的时候,在主程序结束前通过channel来通知这些goroutine关闭。
2、用于在主程序获取当有一个或者任意个数的协程执行完毕,然后执行后续操作context
- 描述
done channel 可以在你的程序中流动并取消所有阻塞的并发操作。 看起来不错,但是还不完美,还不是很够用。
如果我们可以在简单的通知上附加传递额外的信息; 如 为什么取消发生, 函数是否有需要完成的最后期限(超时), 这些情况下这些功能 非常有用。 - 函数中取消有三种情况:
1。 goroutine 的 父 goroutine 可能想要取消它。
2。 一个goroutine 可能想要取消它的 子goroutine
3。 goroutine 中呢任何阻塞操作都必须是可抢占的, 以便它可以被取消。 - 适用范围:
context相比于单独使用channel来通知goroutine关闭有着更加复杂的功能,适用于通知有多层的goroutine关闭。
http、tcp、udp
网络概念
1、七层模型:
有应用层 表示层 会话层 传输层 网络层 数据链路层 物理层
大致举例
2、4层模型(tcp/ip协议族):
应用层,传输层,网络层,网络接入层四个层次
3、http属于应用层、tcp和udp属于传输层
4、IP (网际协议)、TCP(传输控制协议)、HTTP(应用层协议)、UDP(用户数据报协议)
5、传输层用来传输网络数据,应用层用来定义传输的网络数据的规则使其有意义
http
1、属于应用层
2、http基于tcp通过三次握手建立链接
3、www浏览的一个协议
4、http/1.1开始使用长链接
重要概念
1.连接(Connection):一个传输层的实际环流,它是建立在两个相互通讯的应用程序之间。
2.消息(Message):HTTP通讯的基本单位,包括一个结构化的八元组序列并通过连接传输。
3.请求(Request):一个从客户端到服务器的请求信息包括应用于资源的方法、资源的标识符和协议的版本号
4.响应(Response):一个从服务器返回的信息包括HTTP协议的版本号、请求的状态(例如“成功”或“没找到”)和文档的MIME类型。
5.资源(Resource):由URI标识的网络数据对象或服务。
6.实体(Entity):数据资源或来自服务资源的回映的一种特殊表示方法,它可能被包围在一个请求或响应信息中。一个实体包括实体头信息和实体的本身内容。
7.客户机(Client):一个为发送请求目的而建立连接的应用程序。
8.用户代理(Useragent):初始化一个请求的客户机。它们是浏览器、编辑器或其它用户工具。
9.服务器(Server):一个接受连接并对请求返回信息的应用程序。
10.源服务器(Originserver):是一个给定资源可以在其上驻留或被创建的服务器。
11.代理(Proxy):一个中间程序,它可以充当一个服务器,也可以充当一个客户机,为其它客户机建立请求。请求是通过可能的翻译在内部或经过传递到其它的服务器中。一个代理在发送请求信息之前,必须解释并且如果可能重写它。
代理经常作为通过防火墙的客户机端的门户,代理还可以作为一个帮助应用来通过协议处理没有被用户代理完成的请求。
12.网关(Gateway):一个作为其它服务器中间媒介的服务器。与代理不同的是,网关接受请求就好象对被请求的资源来说它就是源服务器;发出请求的客户机并没有意识到它在同网关打交道。
网关经常作为通过防火墙的服务器端的门户,网关还可以作为一个协议翻译器以便存取那些存储在非HTTP系统中的资源。
13.通道(Tunnel):是作为两个连接中继的中介程序。一旦激活,通道便被认为不属于HTTP通讯,尽管通道可能是被一个HTTP请求初始化的。当被中继的连接两端关闭时,通道便消失。当一个门户(Portal)必须存在或中介(Intermediary)不能解释中继的通讯时通道被经常使用。
14.缓存(Cache):反应信息的局域存储。
tcp
1、属于传输层
2、面向链接,三次握手进行链接通信
3、对比udp速度慢、传输可靠、适用传输大量数据
重要概念
- *源端口和目的端口字段—— socket(IP+端口号)。TCP的包是没有IP地址的,那是IP层上的事。但是有源端口和目标端口。
- *序列号 SEQ ——当前报文段的序号。
- 确认应答号 AN ——期望收到对方的下一个报文段的数据的第一个字节的序号;
- 紧急 URG ——当 URG = 1 时,表明紧急指针字段有效。它告诉系统此报文段中有紧急数据,应尽快传送(相当于高优先级的数据);
- *确认 ACK ——当 ACK = 1 时。表示确认应答号 AN 有效。
- 推送 PSH (PuSH) —— 接收 TCP 收到 PSH = 1 的报文段,就尽快地交付接收应用进程,而不再等到整个缓存都填满了后再向上交付;
- 复位 RST (ReSeT) —— 当 RST = 1 时,表明 TCP 连接中出现严重差错(如由于主机崩溃或其他原因),必须释放连接,然后再重新建立传输连接;
- *同步 SYN —— 同步 SYN = 1 表示这是一个连接请求报文。
- 终止 FIN (Finish) —— 用来释放一个连接。FIN= 1 表明发送端的数据已发送完毕,并要求释放传输连接;
- 窗口字段 —— 占 2 字节,用来让对方设置发送窗口的依据,单位为字节。窗口值是[ 0, 216-1 ]之间的整数;
- 检验和 —— 占 2 字节。检验和字段检验的范围包括首部和数据这两部分。在计算检验和时,要在TCP 报文段的前面加上 12 字节的伪部(协议字段为6,表示TCP);
- 紧急指针字段 —— 占 16 位,指出在本报文段中紧急数据共有多少个字节(紧急数据放在本报文段数据的最前面);
- 选项字段 —— 长度可变。① 最大报文段长度 MSS:MSS是指在TCP连接建立时,收发双发协商的通信时每一个报文段所能承载的数据字段的最大长度(并不是TCP报文段的最大长度,而是:MSS=TCP报文段长度-TCP首部长度),单位为字节(双方提供的MSS中的最小值,为本次连接的最大MSS值);② 窗口扩大选项;③ 时间戳选项; ④ 选择确认选项;
tcp三次握手
- 第一次握手:客户端向服务器发送请求报文段,其中同步位SYN=1,序号SEQ=x(表明传送数据时的第一个数据字节的序号是x),等待服务器确认;
- 第二次握手:服务器收到客户端发来的请求,如果同意建立连接,就发回一个确认报文段,该报文段中同步位SYN=1,确认号ACK=x+1,序号SEQ=y;
- 第三次握手:客户端收到服务器的确认报文段后,还需要向服务器给出确认,向其发送确认包ACK(ack=y+1),进而完成三次握手。
- 通过这样的三次握手,客户端与服务端建立起可靠的双工的连接,开始传送数据。
- 为了保证服务端能收接受到客户端的信息并能做出正确的应答而进行前两次(第一次和第二次)握手
- 为了保证客户端能够接收到服务端的信息并能做出正确的应答而进行后两次(第二次和第三次)握手。
udp
1、属于传输层
2、非面向链接,直接通过ip发送消息
3、速度快、传输不可靠、适用传输少量数据允许丢包
docker安装php开发环境
序言
- 本文继续mac os系统演示
- 本文基于本地开发安装环境用于演示
- nginx-1.19.3、php-7.3、redis:6.0
一、安装docker
1
$ brew cask install docker
二、安装需要了解的概念和基础命令
1、docker的组成
docker由 仓库、镜像、容器三大部分组成
1)镜像:可以想想成为模版,例如windows系统的镜像,
2)容器:通过启动镜像来生成容器,就是我们环境中真实要使用到的服务存储的地方 一般由 一个linux系统内包含一个应用服务组成(例如linux中安装了一个php)
3)仓库:用来集中存储镜像的的地方,一般我们使用 docker hub (hub.docker.com)
容器与镜像的关系类似于面向对象编程中的对象与类 - 如果用git来对比举例:
镜像则为一个维护好的master主分支
容器则是我们自己定义使用的开发分支,但是不能合并到主分支
仓库则为git的代码存放仓库了2、环境的安装方式
1)使用docker安装一个linux虚拟机,然后在虚拟机中使用linux相关命令安装php相关开发环境,这就和docker没啥太大关系了
2)使用docker分别nginx、PHP、redis等多个应用服务容器然后用docker的网关进行链接通信3、环境安装方法
- 分为普通基础安装:
一个一个安装,官方样例经常出现的方式 - 使用docker-composer安装:
使用docker compose 配置文件(yml文件),配置应用程序需要的所有服务,然后使用一个命令自动安装所有东西(例如php 的composer.json 和compose install) - 本文会先介绍基础安装,然后介绍 docker-composer 方式
4、常用基础命令
1) docker images(docker image ls) :来列出本地主机上已经下载的镜像
2) docker pull :用来从远程仓库下载镜像
3) docker search :在仓库中搜索镜像
4)docker run :运行镜像生成并生成容器
5)docker ps :查看正在运行的容器的列表
6)docker ps -a:查看本地已经生成的容器(包含未运行的)
7)docker exec :进入到容器中,可以想想未ssh进入到了一个linux系统
三、安装nginx
1、下载nginx镜像
1 | $ docker pull nginx:1.19.3 |
这样nginx镜像就下载下来了,可以用 docker images 查看
2、运行镜像生成容器
1 | $ docker run --name nginx-1.19.3 |
参数说明:
-v 表示目录映射关系(前者是宿主机目录,后者是映射到宿主机上的目录),可以使用多个-v做多个目录或文件映射。注意:最好做目录映射,在宿主机上做修改,然后共享到容器上
–name nginx-1.19.3:设置容器名称。
-p 80:80: 端口进行映射,将本地 80 端口映射到容器内部的 80 端口。
-d: 设置容器在在后台一直运行。
3、创建配置文件、进入容器逛一逛
1 | $ docker exec -it nginx-1.19.3 |
进来后可以看看nginx的配置文件等信息,
现在我们已经把配置文件映射到宿主机上了我们可一到宿主机的对应目录调调改改nginx配置文件
1 | #宿主机 config |
然后exit退出 ,然后重启下nginx
1 | $ docker restart nginx-1.19.3 |
现在我们就可以运行我们的nginx了 127.0.0.1
但是现在只能运行查看html等静态文件,看不了php,这是为什么呢?因为我们还没装php
三、安装php
1、下载php
1 | $ docker pull php:7.3 |
2、运行镜像生成容器
1 | $ docker run --name php-7.3 |
参数说明:
-expose 对其他容器报漏9000端口,但不能和主机通信 。
重点说明:
-此处php挂在的目录需要与nginx中的www目录相同,因为正常来说nginx和php在一台机器上,www目录要php和nginx都能访问到
-可以运行后发现好多扩展没有,可以使用进入docker容器使用docker提供的命令进行安装 例如mysql
因为使用的源是fpm包 所以很多扩展都没有装
1 | $ docker-php-ext-install pdo |
四、容器和容器间通信
现在已经安装了nginx和php了为啥还是不能解析php文件呢
原因在于nginx和php两个容器之间还有没进行通信
那么怎么进行通信呢
1、我们要修修改nginx中fastcgi中请求php的地址参数 fastcgi_pass 127.0.0.1:9000; 改为 fastcgi_pass 172.25.0.2:9000;
2、重启nginx,就可以了呀那么问题来了。这个php的ip哪来的。
我们默认使用docker的bridge模式 此网络创建一个名为docker0的虚拟网桥,并将容器连接到一个docker0虚拟网桥,所有网桥下的容器共享Network Namespace五、使用docker compose 安装nginx、php
上面已经大致的了解了怎么安装nginx和php了 ,mysql和上面的方法一样我就不举例了
下面说如何使用compose进行安装呢?compose安装都需要什么呢?
1、compose安装环境的流程
使用 Dockerfile 定义应用程序的环境。
使用 docker-compose.yml 定义构成应用程序的服务,这样它们可以在隔离环境中一起运行。
最后,执行 docker-compose up 命令来启动并运行整个应用程序。
Dockerfile 这个玩意没有也能装,他是用来自定义镜像的,我们用通用镜像就可以
2、上docker-compose.yml 文件
- 随便找个地方件个文件夹,进去到里面创建的xxxx.yml文件,我的叫composetest.yml
1 | version: "3" |
参数解释
version:这个是表示用哪个docker-compose版本来解析,不用版本模版语法有差异 这里用的3
networks:用来声明所使用的网络环境
services:这个就是服务容器的集合,里面每一块都是一个服务
container_name:用来定一容器的名称
image:表示锁使用镜像的,也可以使用build命令来指定dockerfile文件路径从上下文路径 ./Dockerfile 所构建镜像
volumes:挂载
services 中的networks这里来指定使用哪个网络,指定当前容器在当前网段的ip
expose:对外报漏的端口
ports:宿主机和容器的端口映射注意事项
1) 如果指定ip需要自己创建一个网络空间 不能使用默认的,这也解决了每次容器ip变动的问题1
docker network local_net bridge
2) 关于虚拟域名配置
这就要做一下nginx容器的host配置如上yml文件中extra_hosts,当前宿主机的本地host也要加
然后修改nginx的配置 如下是我的meidaifu.conf的配置
1 | server { |
然后修改yml文件 中nginx的配置
1 | nginx: |
3、运行docker compose
1 | docker-compose up -d |
然后就可以运行了。
如果可以在果面版的docker中查看的更清楚
docker-compose down 停止并删除容器
docker-compose stop 停止并不删除容器
4、使用dockerfile自动增加PHP扩展
当前目录创建 phpfile/Dockerfile
1 | # base image |
然后修改 compose文件中php的配置为
1 | php: |
build 和image 同时出现 则image 将为build后镜像生成的名字和tag
然后
1 | docker-compose up -d |
然后你就可以拿这你的配置文件 到处耍了。
六、注意事项
查看另一片文章docker遇到的相关问题
七、延伸思考
1、这种搭建方式是否符合最佳事实践方案标准?
2、如果多台服务器需要部署如何管理?
docker遇到的一些问题
docker个人遇到的问题一些备注
1) 如果出现 bash: vim : command not found 需要安装下vim
1 | $ apt-get update |
2) 如果出现 bash: apt-get : command not found 尝试使用yum安装
1 | $ yum install vim -y |
3) 如果容器内出现 [Errno 13] Permission denied (错误描述如下)
1 | $ yum install vim -y |
- 是因为进入用户不是root权限,进入容器时增加 –user root 参数 (docker exec -it –user root 容器 bash)
4) 查看所有容器的ip
1 | $ docker inspect --format='{{.Name}} - {{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' $(docker ps -aq) |
5) docker中安装php扩展
1 | $ docker-php-ext-install pdo |
6) 提示文件挂载目录不可使用
可以查看一下doc可以挂载的授权目录的列表可以在桌面版上看一下
7) 如果创建网络出现 ERROR: Pool overlaps with other one on this address space
该错误的意思是docker已有一个容器占用了目前docker-compose里的subnet。
1 | docker network prune |
7) 创建一个自己的网络
1 | docker network create --subnet=172.25.0.0/24 --driver=bridge local_net |
docker网络模式
bridge模式(默认)(–network=bridge)
- 创建一个名为docker0的虚拟网桥,并将容器连接到一个docker0虚拟网桥,所有网桥下的容器共享Network Namespace
elasticsearch知识整理
ElasticSearch
elasticsearch 是一个基于Lucene的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。Elasticsearch是用Java开发的,并作为Apache许可条款下的开放源码发布,是当前流行的企业级搜索引擎。
特点
- 分布式,无需人工搭建集群(solr就需要人为配置,使用Zookeeper作为注册中心)
- Restful风格,一切API都遵循Rest原则,容易上手
- 近实时搜索,数据更新在Elasticsearch中几乎是完全同步的。
一、安装
1、docker方式安装elasticsearch
- 安装
1
$ docker pull elasticsearch
- 启动 (java程序访问可以在增加一个9300端口)
1
$ docker run --name elasticsearch -p 9200:9200 -e "discovery.type=single-node" -d elasticsearch
- 进入容器修改配置
1
2
3$ docker exec -it elasticsearch /bin/bash
$ cd /usr/share/elasticsearch/config/
$ vi elasticsearch.yml - 追加一下内容,解决跨域问题
1
2http.cors.enabled: true
http.cors.allow-origin: "*" - 重启容器默认访问链接http://127.0.0.1:9200就可以打开kibana的界面了。
1
2$ exit
$ docker restart elasticsearch2、安装ik分词器
es自带的分词器对中文分词不是很友好,elasticsearch的版本和ik分词器的版本需要保持一致;1
2
3
4$ cd /usr/share/elasticsearch/plugins/
$ elasticsearch-plugin install 对应版本的安装包地址(https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.2.0/elasticsearch-analysis-ik-7.2.0.zip)
$ exit
$ docker restart elasticsearch 可以在kibana界面的dev tools中验证是否安装成功3、docker安装kibana
可视化elasticsearch管理页面 - 安装 (要与es版本对应)
1
$ docker pull kibana
- 启动kibana(使用–link连接到elasticsearch容器 ,或者使用Network让其在同一个网络下) 浏览器输入http://127.0.0.1:5601就可以打开kibana的界面了。
1
2$ docker run --name kibana -p 5601:5601 -d kibana
$ docker start kibana - 遇到问题
1)访问提示 Kibana server is not ready yet
第一:把KB和ES版本调整为统一版本
第二:将配置文件kibana.yml中的elasticsearch.url改为正确的链接
第三:保持kibana和elasticsearch在同一个网络模式下,可以使用默认的bridge网络下的段的地址172.17.0.x
注意,默认为bridge网络,此网络下两个容器无法用127.0.0.x 的本地ip进行通信访问,如果想用要都切换到host模式
二、常用命令
1 | GET _search |
三、索引settings和mappings
1 | PUT loaravel-log-2020.11.05 |
四、重要知识点
查询搜索条件算分
索引的命名规则
创建一个新indx时 设置 setting mapping(字段类型,分词规则)
index template
聚合统计搜索使用(aggs) bucket按照规则诗句划分不同的组(相当于mysql group),metric 用于数据集的统计(可以统计bucket生成的数据集中的字段进行分析,例如平均值)
search -》协调节点-》查询所有分片-》取回id和排序合并-》去对应分片的数据节点取出具体doc信息
主节点 index(存储索引index信息,索引关联对应的分片) -》
分布式知识整理
一、分布式事务
1、tcc(最终一致性)
- 一般使用tcc分布式事务框架
- 实现的功能需要修改的代码比较多,需要个个系统配合完成try、commit和cancel方法
- 有try、commit和cancel三个阶段
- TCC是业务层面的分布式事务,最终一致性,不会一直持有资源的锁。
- try阶段保证各自服务本地资源一致
- commit 和cancel只会有一个被执行
- try阶段有服务失败将触发cancel 如果cancel失败,那么走补偿机制,保证最终一致性
- 不会阻塞资源
- 执行流程
1) 成功流程:事务框架->try(请求所有服务预留业务资源,不阻塞)->成功->commit(所有服务正式提交修改)
2) 失败流程:事务框架->try(请求所有服务预留业务资源,不阻塞)->失败->cancel(告知所有服务取消预约)
3) commit/cancel 如果失败了,那么走补偿机制,保证最终一致性3、xa两段提交(强一致性)(阻塞)
- 分为协调者(xa协调工具)、参与者(各服务)
- XA是资源层面的分布式事务,强一致性,在两阶段提交的整个过程中,一直会持有资源的锁。
- 只有try 和commit 两个阶段
- MySQL中只有InnoDB引擎支持XA协议
- MySQL中只有当隔离级别为Serializable(最高界别,串行)时候才能使用分布式事务
- 执行流程
1) 成功流程:协调者->try(询问所有服务是否可以执行,持有资源的锁)->成功->协调者通知commit(所有服务正式提交修改),解除资源锁定
2) 失败流程:协调者->try(询问所有服务是否可以执行,持有资源的锁)->失败->协调者通知rollback,解除资源锁定
2、mq事务消息(最终一致性)
- 实现的功能需要修改的代码相对较少,消费端几乎不需要任何修改
- 只能保证(生产端的业务和消息投递到mq同时成功)
- 消费端消费失败走补偿机制,保证最终一致性
- 执行流程
1) 服务a向mq发送prepare消息(本地事务,消费端不会收到)->mq得到返回给服务a执行结果->只能服务a本地业务
2) 服务a本地业务执行成功->向mq发送事务消息的提交commitlog->消息出现在真实的mq消息中->消费端接收处理
3) 服务a本地业务执行失败->向mq发送事务消息的撤销commitlog->移除事务消息
4) 如果mq未收到事务的commit/rollback则会回查事务状态,然后继续执行4、最大努力通知
- 不可靠消息:业务活动主动方,在完成业务处理之后,向业务活动的被动方发送消息,直到通知N次后不再通知,允许消息丢失(不可靠消息)。
- 定期校对:业务活动的被动方,根据定时策略,向业务活动主动方查询(主动方提供查询接口),恢复丢失的业务消息。
5、可靠消息最终一致性
- 是指产生消息的业务动作与消息发送的一致
- 如果业务操作成功,那么由这个业务操作所产生的消息一定要成功投递出去
- 异步确保性
- 一般可以使用mq事务消息实现
二、分布式锁
1、基于缓存(Redis等)实现分布式锁
- 主要使用命令:
1)SETNX key val:当且仅当key不存在时,set一个key为val的字符串,返回1;若key存在,则什么都不做,返回0。
2)expire key timeout:为key设置一个超时时间,单位为second,超过这个时间锁会自动释放,避免死锁。
2)delete key :删除key。 - 实现思想:
1)获取锁的时候,使用setnx加锁,并使用expire命令为锁添加一个超时时间,超过该时间则自动释放锁,锁的value值为一个随机生成的UUID,通过此在释放锁的时候进行判断。
2)获取锁的时候还设置一个获取的超时时间,若超过这个时间则放弃获取锁。
3)释放锁的时候,通过UUID判断是不是该锁,若是该锁,则执行delete进行锁释放。
2、基于Zookeeper实现分布式锁
因为需要频繁的创建和删除节点,性能上不如Redis方式。
- 实现思想:
1)创建一个目录mylock;
2)线程A想获取锁就在mylock目录下创建临时顺序节点;
3)获取mylock目录下所有的子节点,然后获取比自己小的兄弟节点,如果不存在,则说明当前线程顺序号最小,获得锁;
4)线程B获取所有节点,判断自己不是最小节点,设置监听比自己次小的节点;
5)线程A处理完,删除自己的节点,线程B监听到变更事件,判断自己是不是最小的节点,如果是则获得锁。
3、基于数据库实现分布式锁;
性能差,不推荐,不需要了解
redis知识点整理(持续更新)
redis
一、redis支持的数据类型
string(字符串)
- 描述:
二进制安全,基本的常用的数据类型,大能存储 512MB - 基本用法:
1、写入: SET key “value”
2、读取: GET key - 使用场景:
常用的kv存取hash(哈希)
- 描述:
是一个键值(key=>value)对集合,也就是一个key对应多个kv - 基本用法:
1、写入:
多个kv:HMSET hash_key field_key1 “val1” field_key2 “val2”
一个kv:HSET hash_key field_key3 “val3”
2、读取:
HGET hash_key field_key1 - 使用场景:
例如存用户信息list(列表)
- 描述:
按照插入顺序排序,你可以添加一个元素到列表的头部(左边)或者尾部(右边),常用实现队列,一个列表最多可以包含 232 - 1 个元素 (4294967295, 每个列表超过40亿个元素) - 基本用法:
1、头部插入:
LPUSH listKey val1
LPUSH listKey val2
LPUSH listKey val3
尾部取出:
RPOP listKey //val3
2、尾部插入:
RPUSH listKey val1
RPUSH listKey val2
RPUSH listKey val3
头部取出:
RPOP listKey //val1
使用场景:
队列实现,消息队列,商品抢购等等需要有序排队的情况set(集合)
描述:
String 类型的无序集合。成员是唯一的,不能出现重复的数据。基本用法:
1、写入:
SADD set_key val1 [val…]
SADD set_key val2 [val…]
2、读取:
SMEMBERS set_key //读所有合集中的成员
SCARD set_key //获取集合的成员数
SREM set_key val2 [val…] //移除集合中一个或多个成员使用场景:
例如存一个人的粉丝,交集、并集、差集等操作,可以非常方便的实现如共同关注等功能zset(sorted set:有序集合)
描述:
String 类型的有序集合。不能出现重复的数据,每个元素都会关联一个 double 类型的分数(score),分数可重复常用命令:
zadd(key, score, member):向名称为key的zset中添加元素member,score用于排序。如果该元素已经存在,则根据score更新该元素的顺序。
zrem(key, member) :删除名称为key的zset中的元素member
zincrby(key, increment, member) :如果在名称为key的zset中已经存在元素member,则该元素的score增加increment;否则向集合中添加该元素,其score的值为increment
zrank(key, member) :返回名称为key的zset(元素已按score从小到大排序)中member元素的rank(即index,从0开始),若没有member元素,返回“nil”
zrevrank(key, member) :返回名称为key的zset(元素已按score从大到小排序)中member元素的rank(即index,从0开始),若没有member元素,返回“nil”
zrange(key, start, end):返回名称为key的zset(元素已按score从小到大排序)中的index从start到end的所有元素
zrevrange(key, start, end):返回名称为key的zset(元素已按score从大到小排序)中的index从start到end的所有元素
zrangebyscore(key, min, max):返回名称为key的zset中score >= min且score <= max的所有元素
zcard(key):返回名称为key的zset的基数
zscore(key, element):返回名称为key的zset中元素element的score
zremrangebyrank(key, min, max):删除名称为key的zset中rank >= min且rank <= max的所有元素
zremrangebyscore(key, min, max) :删除名称为key的zset中score >= min且score <= max的所有元素使用场景:
例如排行榜跳表(集合对象的一种编码结构,skiplist,还有一种ziplist)
1、元素数量小于128 、所有member长度都小于64 使用ziplist
2、数据量大时针对redis的有序集合和zset的底层依赖是跳表实现的
3、zset支持快速的插入和删除 首选链表
4、zset支持范围查询:让链表排序,但是查找的时间复杂度O(n) 效率不高,要满足范围查询的话效率也比较低
5、为了效率为链表增加索引,如果只增加一层索引的话数据量大的时候效率还是很慢,所以要增加多级索引
6、所以这种链表增加多级索引的结构就是跳表
7、 结构上来讲:跳表在原有的链表上每两个增加一个一个指针
8、根据多层链表的想法可以在继续增加层数,增加节点和节点指针的链接的跨度,类似二分查找
9、为了解决插入会打乱链接结构问题问题,在插入时会随机出来一个要插入的层数,这样新插入的节点不会影响其他节点的层数,只需要修改新节点的前后指针二、缓存穿透、缓存击穿、缓存雪崩区别
1、缓存穿透(缓存和数据库中都没有的数据):
描述:
缓存和数据库中都没有的数据,而用户不断发起请求。常规情况存储层查不到数据则不写入缓存,导致这个不存在的数据每次请求都要到存储层去查询解决:
1)增加查询参数校验,避免一些超过常规情况和非法的参数请求进来,例如(id:-1)
2)针对在数据库中找不到记录的,仍然将该空数据存入缓存中,设置一个较短的过期时间,如30秒(设置太长会导致正常情况也没法使用)
3)布隆过滤器(Bloom Filter),可以快速确定数据是否存在2、缓存击穿(缓存没有数据和数据库中有数据,热门key,一般是缓存时间到期):
描述:
缓存击穿表示某个key的缓存非常热门,有很高的并发一直在访问,如果该缓存失效,那同时会走数据库,压垮数据库。解决:
1、让该热门key的缓存永不过期。
2、增加根据key增加互斥锁,通过redis的setnx实现互斥锁3、缓存雪崩(大面积缓存同时到期)
描述:
大面积缓存同时到期,导致大量请求来访问数据库解决:
1、设置缓存过期时间时加上一个随机值,避免缓存在同一时间过期三、HyperLogLog使用与应用场景
1、HyperLogLog:
用来做基数统计的算法,结果有 0.81%的误差,和bitmap相比更节省内存
bitmap用来数组来表示各元素是否出现
HyperLogLog基数统计的算法(去重后的次数),非redis独有
占用内存小
hyperloglog key占用了12K的内存用于标记基数
2、基本使用
pfadd 添加 :pfadd m1 1 2 3 4 1 2 3 2 2 2 2
pfcount 获得基数值: pfcount m1 //4
pfmerge 合并多个key:
pfadd m1 1 2 3 4 1 2 3 2 2 2 2 //1
pfcount m1 //4
pfadd m2 3 3 3 4 4 4 5 5 5 6 6 6 1 //1
pfcount m2 //5
pfmerge mergeDes m1 m2 //ok
pfcount mergeDes //62、使用场景
需要统计的数据基数很大
可以和bitmap配合使用,bitmap标示存在的元素,hyperloglog用来计数
统计注册IP数、在线人数、每日访问的uv、ip等
四、redis的集群高可用方案
1、主从模式:
一个master可以拥有多个slave,但是一个slave只能对应一个master
主可以读写,从只能读缺点:
主库挂了以后服务将不能写入2、Sentinel模式(哨兵):
建立在主从模式的基础上,监听redis的mater的状态,如果master挂掉了,会在slave上选出一台机器作为master
使用时需要连接sentinel的ip和port而不是redis的
3、Cluster模式:
在需要更大规模的存储的时候主从模式或sentinel模式就不能很好的满足需求了
cluster模式的解决单机Redis容量有限的问题,将Redis的数据根据一定的规则分配到多台机器
redis版本低的话需要安装对应版本的ruby
去中心化的,每个节点(master)都是平等的,连接哪个节点都可以获取和设置数据。
四、内存回收机制
1、内存使用达到maxmemory上限时候触发的溢出回收策略
- noeviction(默认): 不会删除任何数据,拒绝所有写入操作并返 回客户端错误信息,此 时Redis只响应读操作。
- volatile-lru: 根据LRU算法删除设置了超时属性(expire)的键,直到腾出足够空间为止。如果没有可删除的键对象,回退到noeviction策略。
- volatile-ttl: 根据键值对象的ttl属性,删除最近将要过期数据。如果没有,回退到noeviction策略。
- allkeys-lru: 根据LRU算法删除键,不管数据有没有设置超时属性,直到腾出足够空间为止。
- volatile-random: 随机删除过期键,直到腾出足够空间为止。
- allkeys-random: 随机删除所有键,直到腾出足够空间为止。
*** volatile-xxx 策略只会针对带过期时间的 key 进行淘汰,allkeys-xxx 策略会对所有的 key 进行淘汰
2、删除过期时间的键对象
- 惰性删除 : 当用户访问已经过期的对象的时候才删除
- 定时任务删除 : 内部维护了一个定时任务,默认每秒运行10次
1)定时任务在每个数据库空间采用慢模式随机检查20个键,当发现过期时,删除对应的键。
2)如果超过检查数25%的键过期,循环执行回收逻辑直到不足25%或 运行超时为止,慢模式下超时时间为25毫秒。
3)如果之前回收键逻辑超时,则在Redis触发内部事件之前再次以快模式运行回收过期键任务,快模式下超时时间为1毫秒且2秒内只能运行1次。
4)快慢两种模式内部删除逻辑相同,只是执行的超时时间不同
五、其他知识点汇总
1、redis实现延时队列
使用有序合集,使用时间戳做score, 消息内容作为 key
消费者使用 zrangbyscore 获取当前时间之前的数据做轮询处理。
mysql知识点整理(持续更新)
mysql
一、锁相关
1、悲观锁:
- 悲观的认为每次操作数据的时候都会有人来修改数据,所以使用数据时对其加锁,确保在自己使用的过程中数据不会被别人修改,使用完成后进行数据解锁。由于数据进行加锁,期间对该数据进行读写的其他线程都会进行等待。写入频繁使用悲观锁
2、乐观锁:
- 所以每次获取数据的时候都不会进行加锁,但是在更新数据的时候需要判断该数据是否被别人修改过。一般是在sql的where上带版本号或时间戳,写代码时候自己实现,读取频繁使用乐观锁
3、共享锁:(自己读写,其他读)
- 如果事务T对数据A加上共享锁后,则其他事务只能对A再加共享锁,不能加排他锁。获准共享锁的事务只能读数据,不能修改数据。
4、排他锁:(只能自己读写)
- 如果事务T对数据A加上排他锁后,则其他事务不能再对A加任任何类型的封锁。获准排他锁的事务既能读数据,又能修改数据。
5、简要说明为什么会发生死锁?
- 若干事务相互等待释放封锁,就陷入无限期等待状态,系统就进入死锁
6、解决死锁的方法应从预防和解除的两个方面着手:
- 死锁的预防方法:
①要求每一个事务必须一次封锁所要使用的全部数据(要么全成功,要么全不成功)
②规定封锁数据的顺序,所有事务必须按这个顺序实行封锁。 - 允许死锁发生,然后解除它,如果发现死锁,则将其中一个代价较小的事物撤消
,回滚这个事务,并释放此事务持有的封锁,使其他事务继续运行。
二、事务隔离级别
1、Read uncommitted(读未提交)(脏读):
- 在该隔离级别,所有事务都可以看到其它未提交事务的执行结果。可能会出现脏读。
2、Read Committed(读已提交,简称: RC)(幻读):
- 一个事务只能看见已经提交事务所做的改变。因为同一事务的其它实例在该实例处理期间可能会有新的 commit,所以可能出现幻读。
3、Repeatable Read(可重复读,简称:RR)(默认,结果相同):
- 这是 MySQL 的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。消除了脏读、不可重复读,默认也不会出现幻读。
4、Serializable(串行):
- 这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。
三、索引
摘自1、重要概念
- 基数:单个列唯一键(distict_keys)的数量叫做基数。
- 回表:当对一个列创建索引之后,索引会包含该列的键值及键值对应行所在的rowid。通过索引中记录的rowid访问表中的数据就叫回表。回表次数太多会严重影响SQL性能,如果回表次数太多,就不应该走索引扫描,应该直接走全表扫描。
EXPLAIN命令结果中的Using Index意味着不会回表,通过索引就可以获得主要的数据。Using Where则意味着需要回表取数据。
2、优化原则
- 适合索引的列是出现在where子句中的列,或者连接子句中指定的列;
- 基数- 较小的类,索引效果较差,没有必要在此列建立索引;
- 使用短索引,如果对长字符串列进行索引,应该指定一个前缀长度,这样能够节省大量索引空间;
- 不要过度索引。索引需要额外的磁盘空间,并降低写操作的性能。在修改表内容的时候,索引会进行更新甚至重构,索引列越多,这个时间就会越长。所以只保持需要的索引有利于查询即可。
四、b-tree索、b-tree索引、hash索引
1、b+tree
- B+ 树是 B 树的变体,定义基本与 B 树一致,与 B 树的不同点
- 所有叶子节点中包含了全部关键字的信息
- 非叶子节点上只存储 key 的信息,这样相对 B 树,可以增加每一页中存储 key 的数量
- B 树是纵向扩展,最终变成一个 “瘦高个”,而 B+ 树是横向扩展的,最终会变成一个 “矮胖子”
- 各叶子节点用指针进行连接
- mysql使用b+树索引
- innodb索引和数据存在一个文件中
- myisam下索引叶子节点保存数据的地址而不是数据本身
2、b-tree
- 多路搜索树,每个结点存储M/2到M个关键字,非叶子结点存储指向关键字范围的子结点;
- 所有关键字在整颗树中出现,且只出现一次,非叶子结点可以命中;
- 树的每个节点都包含key值和data值,叶子节点可以看作外部节点,不包含任何信息;
- 如果 data 比较大时,每一页存储的 key 会比较少;当数据比较多时,同样会有:“要经历多层节点才能查询在叶子节点的数据” 的问题。
3、mysql中的b+tree
- 在B-树基础上,为叶子结点增加链表指针,所有关键字都在叶子结点中出现,非叶子结点作为叶子结点的索引;B+树总是到叶子结点才命中;
- 主聚集索引(clustered index)和辅助索引(secondary index)
- 具体行数据在在聚集索引上,辅助索引叶子节点存储聚集索引的key值,也就是主见
- 辅助索引的叶子节点并不包含行记录的全部数据
- 树的磁盘读写代价更低
- 树的查询效率更加稳定
4、B*树:
- 在B+树基础上,为非叶子结点也增加链表指针,将结点的最低利用率从1/2提高到2/3;
5、mysql中的hash 索引
- Hash索引仅仅能满足“=”,“IN”,“<=>”查询,不能使用范围查询
- 联合索引中,Hash索引不能利用部分索引键查询
- Hash索引无法避免数据的排序操作
- Hash索引任何时候都不能避免表扫描
- Hash索引遇到大量Hash值相等的情况后性能并不一定会比BTree高
五、分库、分表
1、为什么要分表:
- 减小数据库的单表负担,缩短查询时间,减少锁等待的情况。
表垂直分区
- 减少一个表中的字段,根据id关联起来
表水平分区
- 使用规则(例如按id哈希、id区间、时间)分组,可以单库多表或多库多表
分库分表后查询问题
- 单库多表
如果分表为myisam引擎的表,可以创建一个merge引擎的集合表来查询(merge引擎要求被合并表中不能又重复字段)
如果是innodb引擎,只能根据规则单独查询在合并或使用搜索引擎解决 - 多库多表
将数据通过同步插件(例如logstash)或者mq同步到Elasticsearch,然后通过lasticsearch查询,
如果数据量过大可以引入hbase 将数据存到hbase中,然后通过lasticsearch存储hbase的rowkey,然后查到rowkey去hbase中取数据
六、引擎
1、innodb:
- MySQL 5.5 之后的默认存储引擎
- 默认引擎
- 支持行锁
- 索引叶子节点存储数据
- 支持事务
- 默认select不加锁,UPDATE、INSERT、DELETE默认都是排它锁,可以通过(lock in share mode)和(for update)加共享锁和排他锁:
- Record Lock:单个记录上的索引加锁,Gap Lock:间隙锁,对索引项之间的间隙加锁,但不包括记录本身,Next-Key Lock:Gap Lock + Record Lock,锁定一个范围,并且锁定记录本身。
- 如果不通过索引条件检索数据,那么 InnoDB 将对表中所有记录加锁,实际效果跟表锁一样(where不命中索引就锁表)。
- 不擅长快速插入(插入前要排序,消耗时间)
- .ibd文件存放索引和数据
2、myisam:
- MySQL 8.0弃用
- MySQL 5.5 之前的默认存储引擎
- 支持表锁
- 不支持行锁
- 不支持事务
- 索- 引叶子节点只保存数据的地址而不是数据本身
七、explain
1、用途
- 表的读取顺序如何
- 数据读取操作有哪些操作类型
- 哪些索引可以使用
- 哪些索引被实际使用
- 表之间是如何引用
- 每张- 表有多少行被优化器查询
……2、explain包含的字段
- id //select查询的序列号,包含一组数字,表示查询中执行select子句或操作表的顺序
- select_type //查询类型
- table //正在访问哪个表
- partitions //匹配的分区
- type //访问的类型
- possible_keys //显示可能应用在这张表中的索引,一个或多个,但不一定实际使用到
- key //实际使用到的索引,如果为NULL,则没有使用索引
- key_len //表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度
- ref //显示索引的哪一列被使用了,如果可能的话,是一个常数,哪些列或常量被用于查找索引列上的值
- rows //根据表统计信息及索引选用情况,大致估算出找到所需的记录所需读取的行数
- filtered //查询的表行占表的百分比
- Extra //包含不适合在其它列中显示但十分重要的额外信息
八、优化方向
1、正确的字段类型和长度
2、选择何时的存储引擎
3、推荐使用utf8mb4字符集,可以存表情,可以表示4个字节宽位,排序规则上general_ci 更快,unicode_ci 更准确。ci不区分大小写,cs区分大小写
4、少用多表操作(子查询,联合查询),而是将SQL拆分多次执行。如果查询很很小,会增加查询缓存的利用率。
5、慢sql查询处理
定位执行较慢的查询语句
show variables like “slow_query%”;
show variables like “%long_query%”;
开启日志
set global slow_query_log=1;
set long_query_time=0.5;
6、尽量不要出现null值 给默认值 (null不会走索引)
7、索引建立
8、使用explain检查sql的执行情况
9、开启表缓存 query_cache_type=On|1 ,查看show variables like ‘%query_cache%’;
10、主从配置
11、分库,分表
12、查看数据库链接池
九、主从同步延时问题
MySQL数据库主从同步延迟是怎么产生的。
主服务器何以并发, 但是从服务器的里面读取binlog 的线程仅有一个, 当某个SQL在从服务器上执行的时间稍长 或者由于某个SQL要进行锁表就会导致,主服务器的SQL大量积压,未被同步到从服务器里。这就导致了主从不一致, 也就是主从延迟。
如何查看是否存在延迟
1、可以通过监控show slave status\G命令输出的Seconds_Behind_Master参数的值来判断,是否有发生主从延时。:
NULL - 表示io_thread或是sql_thread有任何一个发生故障,也就是该线程的Running状态是No,而非Yes.
0 - 该值为零,是我们极为渴望看到的情况,表示主从复制良好,可以认为lag不存在。
正值 - 表示主从已经出现延时,数字越大表示从库落后主库越多。
负值 - 几乎很少见,这是一个BUG值,该参数是不支持负值的,也就是不应该出现。
MySQL数据库主从同步延迟解决方案。
1、slave的sync_binlog设置为0或者关闭binlog:
- sync_binlog 表示每sync_binlog次事务提交,MySQL调用文件系统的刷新操作将缓存刷下去,1最安全
2、使用比主库更好的硬件设备作为slave。
3、slave的innodb_flush_log_at_trx_commit 设置0: - 默认值1的意思是每一次事务提交或事务外的指令都需要把日志写入(flush)硬盘
- 设置2的意思是不写入硬盘而是写入系统缓存。日志仍然会每秒flush到硬盘,只会在整个操作系统 挂了时才可能丢数据,一般不会丢失超过1-2秒的更新
- 设成0会更快一点,但安全方面比较差,即使MySQL挂了也可能会丢失事务的数据。
4、–logs-slave-updates 从服务器从主服务器接收到的更新不记入它的二进制日志