Go 语言发布
Go每日一库:go-diff
go-diff 主要提供三个功能:
go-diff 不仅能够简洁地输出字符串对比结果,还能够输出规范化的数据结构方便我们的二次开发。
go-diff 使用方式非常简单,代码如下
const (
text1 = "gocn vip"
text2 = "goCN cool"
)
func main() {
dmp := diffmatchpatch.New()
diffs := dmp.DiffMain(text1, text2, false)
fmt.Println(diffs)
}
执行以上代码,输出
[{Equal go} {Insert CN } {Equal c} {Delete n vip} {Insert ool}]
以上输出结果表示从 text1 变成 text2 需要执行的步骤:
DiffMain 方法会查找两段文本的不同,并以数组形式返回 diff 差异。
这里的 diff 差异就是从左边 text1 的字符串变成右边 text2 的字符串所需要的最少的步骤,每个步骤只能做 “保持不变”、“插入” 或者 “删除” 操作。如果我们需要的是替换操作,那么只能是先 “删除” 后 “插入”
工具提供了DiffPrettyText 和DiffPrettyHtml 等方法,可以将 diff 数组转换成更友好的有颜色高亮的文本或 HTML 格式报告。
DiffTimeout参数用以设置 diff 计算的超时时间,如果超过此时间,则该计算将被截断,并返回目前为止的最佳解决方案。 此时尽管结果是正确的,但可能并不是最佳方案。 该值为 0 时可进行无限时的计算。
go-diff 库实现了高效、完备的文本差异对比算法,在类似需求时,如计算编辑距离、模糊匹配时,不需要我们再去手写复杂的算法,非常省心和方便。
JavaScript 的基本类型和引用类型
JavaScript 数据类型目前是有 8 种,在大的方向可以分为两种,一种是基本类型,另外一种是引用类型。
基本类型 在将一个保存着原始值的变量复制给另一个变量时,会将原始值的副本赋值给新变量,此后这两个变量是完全独立的,它们只是拥有相同的 value 而已。
很显然,a 不全等 b
引用类型 在将一个保存着对象内存地址的变量复制给另一个变量时,会把这个内存地址赋值给新变量,也就是说这两个变量都指向了堆内存中的同一个对象,它们中任何一个作出的改变都会反映在另一个身上。(这里要理解的一点就是,复制对象时并不会在堆内存中新生成一个一模一样的对象,只是多了一个保存指向这个对象指针的变量罢了)。多了一个指针
结果然显然,a 全等 b,因为它们的指针指向同一个堆内存
JS 高级程序设计—> 4.1.3 中提到: “ECMAScript 中所有函数的参数都是按值传递的” 结论:没有差别
内部网关协议 (IGP) 和外部网关协议 (EGP) ,言简意赅!
什么是vagrant provision?
从字面上来看,provision是准备,实现的功能是在原生镜像的基础上,进行一些附加的操作,以改变虚拟机的环境,比如安装应用,发布程序等。
在vagrant的 Vagrant.configure(2) do |config| 节点内,加入如下代码:
config.vm.provision "shell", inline: "echo hello provisio."
还有一种格式:
config.vm.provision "shell" do |s|
s.inline = "echo hello provision."
end
测试一下:
如果vm已经启动,直接运行
vagrant provision
就可以看到控制台显示的信息了。或者:
vagrant reload --provision
重启vm,并自动执行provision操作。
Tips: 运行后可能会提示:default: stdin: is not a tty 错误,不影响执行效果,想要去除,在配置文件增加一行即可。
config.ssh.shell = "bash -c 'BASH_ENV=/etc/profile exec bash'"
provision任务是预先设置的一些操作指令,格式:
config.vm.provision 命令字 json格式参数
config.vm.provion 命令字 do |s|
s.参数名 = 参数值
end
每一个 config.vm.provision 命令字
代码段,我们称之为一个provisioner。
根据任务操作的对象,provisioner可以分为:
根据vagrantfile的层次,分为:
Vagrant.configure("2")
的下一层次,形如: config.vm.provision ...
config.vm.define "web" do |web|
的下一层次,web.vm.provision ...
执行的顺序是先执行configure级任务,再执行vm级任务,即便configure级任务在vm定义的下面才定义。例如:
Vagrant.configure("2") do |config|
config.vm.provision "shell", inline: "echo foo"
config.vm.define "web" do |web|
web.vm.provision "shell", inline: "echo bar"
end
config.vm.provision "shell", inline: "echo baz"
end
输出结果:
==> default: "foo"
==> default: "baz"
==> default: "bar"
尝试了helloword,我们来了解一下provision任务是怎么运行的。
run:"always"
属性--provision
参数,适用于 vagrant up
和 vagrant reload
vagrant provision
命令。在编写provision任务时,可能同时存在几种类型的任务,但执行时可能只执行一种,如,我只执行shell类型的任务。可以如下操作:
vagrant provision --provision-with shell
Mysql(双主)主主架构配置
在企业中,数据库高可用一直是企业的重中之重,中小企业很多都是使用mysql主从方案,一主多从,读写分离等,但是单主存在单点故障,从库切换成主库需要作改动。因此,如果是双主或者多主,就会增加mysql入口,增加高可用。不过多主需要考虑自增长ID问题,这个需要特别设置配置文件,比如双主,可以使用奇偶,总之,主之间设置自增长ID相互不冲突就能完美解决自增长ID冲突问题。
1.两台mysql都可读写,互为主备,默认只使用一台(masterA)负责数据的写入,另一台(masterB)备用;
2.masterA是masterB的主库,masterB又是masterA的主库,它们互为主从;
3.两台主库之间做高可用,可以采用keepalived等方案(使用VIP对外提供服务);
4.所有提供服务的从服务器与masterB进行主从同步(双主多从);
5.建议采用高可用策略的时候,masterA或masterB均不因宕机恢复后而抢占VIP(非抢占模式);
这样做可以在一定程度上保证主库的高可用,在一台主库down掉之后,可以在极短的时间内切换到另一台主库上(尽可能减少主库宕机对业务造成的影响),减少了主从同步给线上主库带来的压力;
但是也有几个不足的地方:
1.masterB可能会一直处于空闲状态(可以用它当从库,负责部分查询);
2.主库后面提供服务的从库要等masterB先同步完了数据后才能去masterB上去同步数据,这样可能会造成一定程度的同步延时;
1.CentOS 7.3 64位 2台:masterA(192.168.59.132),masterB(192.168.59.133)
2.官方Mysql5.6版本
# wget http://mirrors.sohu.com/mysql/MySQL-5.6/mysql-5.6.35-linux-glibc2.5-x86_64.tar.gz
# ls
# tar zxvf mysql-5.6.35-linux-glibc2.5-x86_64.tar.gz
# mv mysql-5.6.35-linux-glibc2.5-x86_64 /usr/local/mysql
# cd /usr/local/mysql
# useradd mysql
# mkdir -p /data/mysql
# chown -R mysql:mysql /data/mysql
# ./scripts/mysql_install_db --user=mysql --datadir=/data/mysql
在安装上一步中遇到了些错误,这是我之前整理的一个链接 要是还没有解决问题。直接google或者baidu吧!
等待完成后一个非常重要的操作是检查有没有成功~
# cp support-files/my-default.cnf /etc/my.cnf
[root@zhdy-02 mysql]# cp support-files/mysql.server /etc/init.d/mysqld
[root@zhdy-02 mysql]# vi /etc/init.d/mysqld
vim编辑下面两行basedir和datadir配置
basedir=/usr/local/mysql
datadir=/data/mysql
[[email protected] mysql]# chmod 755 /etc/init.d/mysqld
[root@zhdy-02 mysql]# chkconfig --add mysqld
[root@zhdy-02 mysql]# chkconfig --list
[root@zhdy-02 mysql]# /etc/init.d/mysqld start
或者 :
[root@zhdy-02 mysql]# service mysqld start
[root@zhdy-02 mysql]# ps aux |grep mysql
[root@zhdy-02 mysql]# netstat -lntp |grep mysql
2.1 配置 /etc/my.cnf
masterA(192.168.59.132) 配置文件
[[email protected]04 mysql]# vim /etc/my.cnf
server-id=132
log-bin=mysql-bin
auto-increment-increment=2
auto-increment-offset=1
log-slave-updates
重启mysql:
[root@zhdy-04 mysql]# /etc/init.d/mysqld restart
masterB(192.168.59.133) 配置文件
server-id=133
log-bin=mysql-bin
auto-increment-increment=2
auto-increment-offset=2
log-slave-updates
重启:
[root@zhdy-04 mysql]# /etc/init.d/mysqld restart
auto-increment 两行的配置,使 masterA字段产生的数值是 奇数1,3,5,7 下面的masterB 产生的是 2,4,6,8 等,这样会避开双主 id 重复的问题。
2.2 添加主从同步账户
masterA上:
[root@zhdy-04 mysql]# export PATH=$PATH:/usr/local/mysql/bin/
[root@zhdy-04 mysql]# vim /etc/profile
[root@zhdy-04 mysql]# source /etc/profile
[root@zhdy-04 mysql]# mysql -uroot
mysql> grant replication slave on *.* to 'repl'@'192.168.59.133' identified by 'zhangduanya';
mysql> flush privileges;
masterB上:
[root@zhdy-05 mysql]# export PATH=$PATH:/usr/local/mysql/bin/
[root@zhdy-05 mysql]# vim /etc/profile
[root@zhdy-05 mysql]# source /etc/profile
[root@zhdy-05 mysql]# mysql -uroot
mysql> grant replication slave on *.* to 'repl'@'192.168.59.132' identified by 'zhangduanya';
mysql> flush privileges;
2.2 查看主库的状态
masterA上:
mysql> show master status;
+------------------+----------+--------------+------------------+-------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+------------------+----------+--------------+------------------+-------------------+
| mysql-bin.000001 | 208 | | | |
+------------------+----------+--------------+------------------+-------------------+
1 row in set (0.00 sec)
masterB上:
mysql> show master status;
+------------------+----------+--------------+------------------+-------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+------------------+----------+--------------+------------------+-------------------+
| mysql-bin.000001 | 120 | | | |
+------------------+----------+--------------+------------------+-------------------+
1 row in set (0.00 sec)
2.3 配置同步信息:
masterA上:
mysql> change master to master_host='192.168.59.133',master_port=3306,master_user='repl',master_password='zhangduanya',master_log_file='mysql-bin.000001',master_log_pos=120;
mysql> start slave;
mysql> show slave status\G
*************************** 1. row ***************************
Slave_IO_State: Waiting for master to send event
Master_Host: 192.168.59.133
Master_User: repl
Master_Port: 3306
Connect_Retry: 60
Master_Log_File: mysql-bin.000001
Read_Master_Log_Pos: 120
Relay_Log_File: zhdy-04-relay-bin.000002
Relay_Log_Pos: 283
Relay_Master_Log_File: mysql-bin.000001
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
masterB上:
mysql> change master to master_host='192.168.59.132',master_port=3306,master_user='repl',master_password='zhangduanya',master_log_file='mysql-bin.000001',master_log_pos=208;
mysql> start slave;
mysql> show slave status\G
*************************** 1. row ***************************
Slave_IO_State: Waiting for master to send event
Master_Host: 192.168.59.132
Master_User: repl
Master_Port: 3306
Connect_Retry: 60
Master_Log_File: mysql-bin.000001
Read_Master_Log_Pos: 208
Relay_Log_File: zhdy-05-relay-bin.000002
Relay_Log_Pos: 283
Relay_Master_Log_File: mysql-bin.000001
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
我在开始的时候也说了,我用的是测试环境,可以保证没数据写入,否则需要的步骤是:
先masterA锁表-->masterA备份数据-->masterA解锁表 -->masterB导入数据-->masterB设置主从-->查看主从。
3.1 在masterA上创建一个数据库测试同步效果
mysql> create database asd;
Query OK, 1 row affected (0.00 sec)
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| asd |
| mysql |
| performance_schema |
| test |
+--------------------+
5 rows in set (0.00 sec)
3.2 到masterB查看是否已经同步创建数据库
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| asd |
| mysql |
| performance_schema |
| test |
+--------------------+
5 rows in set (0.00 sec)
已经成功!!!
微服务系列:通过Kong网关给API加Key鉴权
上篇文章讲了通过Kong网关给API限流,这篇文章我们就讲讲API的另一个特性:鉴权。
Kong网关作为API网关,提供了多种认证机制以确保API安全性,它允许开发者和企业在API上游和下游之间搭建一个可靠的访问控制系统。以下是Kong网关支持的几种常见的认证类型,以及Key Auth插件的实战。
➢ 密钥认证(Key Authentication): 用户需通过API密钥通过认证才能访问API资源。密钥可以作为请求头、查询参数或者请求体中的一部分发送。
➢ 基础认证(Basic Authentication): 用HTTP标准的基础认证方式,通过用户名和密码组合,经过Base64编码后发送给服务器。
➢ OAuth 2.0认证: 提供了一个安全的访问控制框架,允许用户给第三方应用授权,在不暴露用户凭证的情况下访问API。
➢ LDAP高级认证(LDAP Authentication Advanced): 通过轻量级目录访问协议(LDAP)进行用户认证,通常用于企业环境中用户的身份验证。
➢ OpenID Connect: 基于OAuth 2.0的身份层,用于认证并获取用户基本信息,在现代应用中非常流行。
Key Auth是Kong网关中的一个插件,它将API密钥绑定到消费者对象并通过这个密钥来管理对API资源的访问。在客户端发起请求时,它必须在请求中携带一个有效的API密钥。该密钥可以在请求头(headers)、查询字符串(query string)或请求体(request body)中传输。Key Auth主要在用于客户端认证时保护API,防止未经授权的访问。
使用用户名luka创建一个新的消费者:
curl -i -X POST http://localhost:8001/consumers/ --data username=luka
返回结果如下,表示消费者已创建。
HTTP/1.1 201 Created
Date: Tue, 14 Nov 2023 11:54:54 GMT
Content-Type: application/json; charset=utf-8
Connection: keep-alive
Access-Control-Allow-Origin: *
X-Kong-Admin-Request-ID: GMqdBYiKT8NqXDHhnp4SrRvcQ6LSQZAN
Content-Length: 173
X-Kong-Admin-Latency: 34
Server: kong/3.4.1.1-enterprise-edition
{"custom_id":null,"type":0,"id":"c27cc554-c1a7-4c6e-856f-1f6d44c85480","username":"luka","created_at":1699962894,"updated_at":1699962894,"username_lower":"luka","tags":null}
配置完成后,调用Admin API为新消费者分配一个密钥。将密钥值设置为top-secret-key(仅作为示例):
curl -i -X POST http://localhost:8001/consumers/luka/key-auth --data key=top-secret-key
返回结果如下,表示密钥已经被创建。
HTTP/1.1 201 Created
Date: Tue, 14 Nov 2023 11:56:49 GMT
Content-Type: application/json; charset=utf-8
Connection: keep-alive
Access-Control-Allow-Origin: *
X-Kong-Admin-Request-ID: dqzhpJMcWMsortRn7r8KFiKnezu1Mj4a
Content-Length: 172
X-Kong-Admin-Latency: 17
Server: kong/3.4.1.1-enterprise-edition
{"created_at":1699963008,"key":"top-secret-key","id":"be148421-16e1-48b5-aa95-eed15695da65","ttl":null,"tags":null,"consumer":{"id":"c27cc554-c1a7-4c6e-856f-1f6d44c85480"}}
通过下面命令启用密钥认证,密钥认证插件默认安装在Kong网关上,可以通过向Admin API上的plugins对象发送POST请求来启用:
curl -X POST http://localhost:8001/plugins/ --data"name=key-auth" --data"config.key_names=apikey"
返回结果如下,表示插件已安装。
{
"enabled": true,
"service": null,
"consumer": null,
"name": "key-auth",
"id": "3e70c269-cb62-43e8-bce6-4258d7743eb5",
...
}
使用我们在通过Kong提供API服务一文中提供的URL,完成下面的测试
尝试在未提供密钥的情况下访问服务:
curl -s http://localhost:8000/v1/api/random_value/ | jq
{
"message": "No API key found in request"
}
由于你已经全局启用了密钥认证,你将收到未授权的响应.
尝试使用错误的密钥访问服务:
curl -i http://localhost:8000/v1/api/random_value/ -H 'apikey:bad-key'
HTTP/1.1 401 Unauthorized
Date: Tue, 14 Nov 2023 12:01:12 GMT
Content-Type: application/json; charset=utf-8
Connection: keep-alive
Content-Length: 52
X-Kong-Response-Latency: 1
Server: kong/3.4.1.1-enterprise-edition
{
"message":"Invalid authentication credentials"
}
提示错误的凭据
curl -i http://localhost:8000/abc/anything -H'apikey:top-secret-key'
curl -i http://localhost:8000/v1/api/random_value/ -H'apikey:top-secret-key'
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 57
Connection: keep-alive
Server: gunicorn
Date: Tue, 14 Nov 2023 12:01:59 GMT
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
Referrer-Policy: same-origin
Cross-Origin-Opener-Policy: same-origin
X-Kong-Upstream-Latency: 105
X-Kong-Proxy-Latency: 6
Via: kong/3.4.1.1-enterprise-edition
{"address": "127.0.0.1:8081", "hostname": "7f1edf0ec3f1"}
访问成功。
除此之外,Kong网关还支持基于服务的Key鉴权,也支持基于路由的Key鉴权。大家自行探索
本文讲了Kong网关的Key认证,相对于其他认证方式,这种认证非常简单直观,只需要传递个key,就可以对API鉴权,特别适合API服务的场景。
但是Kong与第三方OAuth对接,需要升级到企业版的Kong,对中小企业不太友好。如果仅仅提供API访问、不需要复杂鉴权,个人认为Kong还是适合使用的。