开发者博客 – IT技术 尽在开发者博客

开发者博客 – 科技是第一生产力


  • 首页

  • 归档

  • 搜索

ADG环境如何打PSU补丁? 一、前言 二、流程

发表于 2021-11-23

「这是我参与11月更文挑战的第23天,活动详情查看:2021最后一次更文挑战」

一、前言

随着Oracle ADG技术的逐渐成熟,大多数数据库环境都使用ADG作为灾备和报表数据库,可以说是标配。

那么如果主数据库由于BUG或者维护,需要更新补丁,该如何去操作呢?

\*本文参考MOS文档:How do you apply a Patchset,PSU or CPU in a Data Guard Physical Standby configuration (Doc ID 278641.1) ***

环境准备:

主机名 ip DB Version db_name db_unique_name
主库 orcl 192.168.56.120 11.2.0.4 orcl orcl
备库 orcl_stby 192.168.56.121 11.2.0.4 orcl orcl_stby

Notes:ADG环境已提前搭建好,并已配置好 DG Broker。

二、流程

主要步骤如下:

1
2
3
4
5
6
bash复制代码1.主库停止日志传输。
2.关闭备库,备库应用补丁程序,备库启动mount,不开启日志应用。
3.关闭主库,主库应用补丁程序。
4.开启主库,开启日志传输。
5.备库开启日志应用。
6.执行检查,确保补丁安装成功。

详细操作步骤如下:

1.主库停止日志传输。

1
2
3
4
5
6
7
bash复制代码##如果已配置DG Broker
dgmgrl sys/oracle@orcl
edit database orcl set state='LOG-TRANSPORT-OFF';


##未配置DG Broker
SQL> alter system set log_archive_dest_state_2=defer scope=both sid='*';

2.关闭备库,备库应用补丁程序,备库启动mount,不开启日志应用。

a.关闭备库实例,监听

1
2
3
4
5
6
7
8
bash复制代码##Non-rac
shutdown immediate
lsnrctl stop

##rac
srvctl stop database -d orcl
srvctl stop listener
srvctl stop scan_listner

b.替换OPatch

1
2
3
4
bash复制代码cd /u01/app/oracle/product/11.2.0/db/
mv OPatch/ OPatch0421
unzip -q /soft/p6880880_112000_Linux-x86-64.zip
opatch version

c.应用补丁程序(根据Readme操作)

1
2
3
4
bash复制代码unzip -q p31537677_112040_Linux-x86-64.zip
cd 31537677
opatch prereq CheckConflictAgainstOHWithDetail -ph ./
opatch apply

如果opatch apply遇到warning,请参照:****11204打PSU时Warning:ins_emagent.mk nmosudo

d.开启备库到mount

1
2
3
4
5
6
7
8
9
10
11
bash复制代码##已配置DG Broker,需要设置APPLY-OFF防止自动开启日志应用
edit database orcl_stby set state='APPLY-OFF';

##Non-rac
startup mount
lsnrctl start

##rac
srvctl start database -d orcl -o mount
srvctl start listener
srvctl start scan_listener

3.关闭主库,主库应用补丁程序。

可参照备库补丁应用过程,此处略过。

4.开启主库,开启日志传输。

a.开启主库,开启监听

b.执行PSU升级脚本

_关于catbundle脚本可参考:Introduction To Oracle Database catbundle.sql (Doc ID 605795.1) _

1
2
3
4
5
6
7
8
9
10
11
sql复制代码--执行catbundle脚本
cd $ORACLE_HOME/rdbms/admin
sqlplus / as sysdba
@catbundle.sql psu apply

--编译无效对象
@utlrp.sql

--执行dbms_java_dev脚本
@dbmsjdev.sql
exec dbms_java_dev.disable

c.开启日志传输

1
bash复制代码edit database orcl set state='ONLINE';

5.备库开启日志应用。

这将使通过catupgrade/catbundle/catcpu脚本对数据库的改变被应用到备库。

1
2
3
4
5
bash复制代码##已配置DG Broker,打开apply-on
edit database orcl_stby set state='ONLINE';

##备库开启到read only,开启ADG
alter database open;

6.执行检查,确保补丁安装成功。

主库:

1
2
bash复制代码opatch lspatches
select action_time,action,version,id,bundle_series,comments from dba_registry_history;

备库:

1
2
bash复制代码opatch lspatches
select action_time,action,version,id,bundle_series,comments from dba_registry_history;

检查ADG同步情况:

1
2
3
4
5
6
7
bash复制代码##已配置DG Broker
show database orcl
show database orcl_stby

##未配置DG Broker
select process,group#,thread#,sequence# from v$managed_standby;
select group#,thread#,sequence#,bytes/1024/1024,archived,status from v$standby_log;

主库删除test表一个记录:

备库查看是否同步:

备库已实时删除,ADG正常同步。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

高级Java程序员必备:《IDEA问题库》常见问题及解决方案

发表于 2021-11-23

这是我参与11月更文挑战的第23天,活动详情查看:2021最后一次更文挑战」

❤️作者简介:大家好,我是小虚竹。Java领域优质创作者🏆,CSDN博客专家认证🏆,华为云享专家认证🏆

❤️技术活,该赏

❤️点赞 👍 收藏 ⭐再看,养成习惯

Intellij IDEA使用教程相关系列 目录

问题:报错unable to establish loopback connection

问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
java复制代码Caused by: java.io.IOException: Unable to establish loopback connection
at sun.nio.ch.PipeImpl$Initializer.run(PipeImpl.java:101)
at sun.nio.ch.PipeImpl$Initializer.run(PipeImpl.java:68)
at java.security.AccessController.doPrivileged(Native Method)
at sun.nio.ch.PipeImpl.<init>(PipeImpl.java:170)
at sun.nio.ch.SelectorProviderImpl.openPipe(SelectorProviderImpl.java:50)
at java.nio.channels.Pipe.open(Pipe.java:155)
at sun.nio.ch.WindowsSelectorImpl.<init>(WindowsSelectorImpl.java:127)
at sun.nio.ch.WindowsSelectorProvider.openSelector(WindowsSelectorProvider.java:44)
at io.netty.channel.nio.NioEventLoop.openSelector(NioEventLoop.java:155)
… 15 more
Caused by: java.net.ConnectException: Connection timed out: connect
at sun.nio.ch.Net.connect0(Native Method)
at sun.nio.ch.Net.connect(Net.java:457)
at sun.nio.ch.Net.connect(Net.java:449)
at sun.nio.ch.SocketChannelImpl.connect(SocketChannelImpl.java:647)
at java.nio.channels.SocketChannel.open(SocketChannel.java:189)
at sun.nio.ch.PipeImpl$Initializer$LoopbackConnector.run(PipeImpl.java:130)
at sun.nio.ch.PipeImpl$Initializer.run(PipeImpl.java:83)
… 23 more

解决方案

关闭防火墙就好了!!!

关闭防火墙就好了!!!

关闭防火墙就好了!!!

问题:IDEA看不到编译后的target文件

问题

控制台提示编译成功,但左边视图没有显示target文件夹

img

img

解决方案

勾选show excluded files,问题就解决了

img

img

问题:严重占用CPU 长时间在90~100%

问题

编译代码或者运行服务时,idea占用大量的CPU

解决方案

关掉不必要的检查,代码检查没必要全部检查,很耗性能

file->settings->editor->inspections

img

问题:debugger启动tomcat 报错Address already in use: NET_Bind

问题

Error running ‘tomcat8’: Unable to open debugger port (127.0.0.1:1441): java.net.BindException “Address already in use: NET_Bind”

解决方案

对于端口占用的情况,解决方案无非以下几种:

  1. 找到是哪个服务占用了端口,如果这个服务不重要,关了这个服务就行;
  2. 如果这个服务重要,就改下我们自己的端口;
  3. 重启IDEA(网管三大绝招第一招);
  4. 重启电脑(网管三大绝招第二招);
  5. 重装电脑(网管三大绝招第三招);

参考

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

LeetCode 22 括号生成【c++/java详细题解

发表于 2021-11-23

「这是我参与11月更文挑战的第23天,活动详情查看:2021最后一次更文挑战」

1、题目

数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。

示例 1:

1
2
‘复制代码输入:n = 3
输出:["((()))","(()())","(())()","()(())","()()()"]

示例 2:

1
2
ini复制代码输入:n = 1
输出:["()"]

提示:

  • 1 <= n <= 8

2、思路

(dfs) O(C2nn)O(C_{2n}^{n})O(C2nn​)

首先我们需要知道一个结论,一个合法的括号序列需要满足两个条件:

  • 1、左右括号数量相等
  • 2、任意前缀中左括号数量 >= 右括号数量 (也就是说每一个右括号总能找到相匹配的左括号)


题目要求我们生成n对的合法括号序列组合,可以考虑使用深度优先搜索,将搜索顺序定义为枚举序列的每一位填什么,那么最终的答案一定是有n个左括号和n个右括号组成。

如何设计dfs搜索函数?

最关键的问题在于搜索序列的当前位时,是选择填写左括号,还是选择填写右括号 ?因为我们已经知道一个合法的括号序列,任意前缀中左括号数量一定 >= 右括号数量,因此,如果左括号数量不大于 n,我们就可以放一个左括号,来等待一个右括号来匹配 。如果右括号数量小于左括号的数量,我们就可以放一个右括号,来使一个右括号和一个左括号相匹配。

递归搜索树如下:


递归函数设计

1
c复制代码void dfs(int n ,int lc, int rc ,string str)

n是括号对数,lc是左括号数量,rc是右括号数量,str是当前维护的合法括号序列。

搜索过程如下:

  • 1、初始时定义序列的左括号数量lc 和右括号数量rc都为0。
  • 2、如果 lc < n,左括号的个数小于n,则在当前序列str后拼接左括号。
  • 3、如果 rc < n && lc > rc , 右括号的个数小于左括号的个数,则在当前序列str后拼接右括号。
  • 4、当lc == n && rc == n 时,将当前合法序列str加入答案数组res中。

时间复杂度分析: 经典的卡特兰数问题,因此时间复杂度为 O(1n+1C2nn)=O(C2nn)O(\frac{1}{n+1}C_{2n}^{n}) = O(C_{2n}^n)O(n+11​C2nn​)=O(C2nn​) 。

3、c++代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
c复制代码class Solution {
public:
vector<string> res; //记录答案
vector<string> generateParenthesis(int n) {
dfs(n , 0 , 0, "");
return res;
}
void dfs(int n ,int lc, int rc ,string str)
{
if( lc == n && rc == n) res.push_back(str);
else
{
if(lc < n) dfs(n, lc + 1, rc, str + "(");
if(rc < n && lc > rc) dfs(n, lc, rc + 1, str + ")");
}
}
};

4、java代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
java复制代码class Solution {
static List<String> res = new ArrayList<String>(); //记录答案

public List<String> generateParenthesis(int n) {
res.clear();
dfs(n, 0, 0, "");
return res;
}
public void dfs(int n ,int lc, int rc ,String str)
{
if( lc == n && rc == n) res.add(str);
else
{
if(lc < n) dfs(n, lc + 1, rc, str + "(");
if(rc < n && lc > rc) dfs(n, lc, rc + 1, str + ")");
}
}
}

原题链接: 22. 括号生成
在这里插入图片描述

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Go 接口 声明接口 基本用法 高级用法 总结

发表于 2021-11-23

Hi,我是行舟,今天和大家一起学习Go语言的接口。

像Channel和协程一样,Go语言的接口设计也是其一大特色。不像Java、C++、PHP等语言,一个类要实现接口必须明确声明,在Go语言中一个类型只要实现了接口中所有的方法,就认为该类型实现了此接口。这种更加松散的实现方式,使面向对象编程变得更加简单。

声明接口

1
2
3
4
go复制代码type 接口名称 interface{
    方法一(传入参数) (返回值)
    方法二(传入参数) (返回值)
}

通过type 接口名称 interface来声明一个接口,在后面的大括号中定义实现接口需要的方法。Go语言的接口只有方法,没有属性。只要实现了所有方法的类型都可以认为实现了此接口,也被戏称Duck typing。

Duck typing:如何判断一个动物是不是鸭子呢?如果一个动物走路、叫声、羽毛、形体和鸭子一样,我们就认为它是一只鸭子。引申一下就是通过判断一个对象的外表和动作决定它的类型。

基本用法

实现接口

例1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
go复制代码// 声明Dog接口,包含getAge方法
type Dog interface{
   getAge()
}

type Jinmao struct {
   age int
}

func (j Jinmao) getAge()  {
   fmt.Printf("I am %d years old!",j.age)
}

func main()  {
   a1 := Jinmao{age:2}
   var d Dog= a1 // 声明Dog类型的实例d,并把a1赋值给d
   d.getAge()// print:I am 2 years old!
}

如上示例,我们声明了Dog接口,Dog接口只包含getAge()方法。接着又定义了Jinmao结构体,Jinmao也实现了getAge方法,所以我们就可以说Jinmao实现了Dog接口。我们定义a1为Jinmao类型,接着定义d为Dog类型,因为a1所属类型实现了Dog接口,所以我们可以把a1赋值给d,d在调用getAge方法时,就会调用Jinmao实现的方法。

示例中有一个关键点需要留意:Jinmao结构体实现的getAge方法不仅仅名称和Dog的getAge方法一致,传入参数和返回值也必须完全一致,才认为Jinmao实现了Dog接口定义的getAge方法。

在Go语言中接口类型的零值是nil。

接口作用

在例1中我们虽然实现了接口,但是并没有体现出使用接口的价值。 例2:

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
go复制代码// 声明Dog接口,包含getAge方法
type Dog interface{
   getAge() int
}

type Jinmao struct {
   age int
}

func (j Jinmao) getAge() int {
   return j.age
}

type Husky struct {
   age int
}

func (h Husky) getAge() int {
   return h.age
}

// 计算所有🐶🐶的年龄和
func AddAge(s []Dog) int {
   var sum int
   for _,v := range s{
      sum += v.getAge()
   }
   return sum
}

func main()  {
   a1 := Jinmao{age:2}
   var d1 Dog= a1 // 声明Dog类型的实例d1,并把a1赋值给d

   a2 := Husky{age: 3}
   var d2 Dog = a2 // 声明Dog类型的实例d2,并把a2赋值给d

   ageSum := AddAge([]Dog{d1,d2})
   fmt.Printf("狗狗年龄之和是:%d", ageSum)
}

如上示例中,我们声明了Jinmao和Husky两个类型且都实现了Dog接口。方法AddAge接收元素为Dog类型的切片。我们声明a1是Jinmao类型并赋值给d1,a2是Husky类型并赋值给d2。接下来调用AddAge方法时候,在AddAge方法内部遍历各Dog类型的元素并调用getAge方法,元素会根据自己被赋予的实际类型执行各自的方法。

实际类型就是接口被赋予的实现此接口实际对象的类型。如例2中的var d1 Dog= a1,d1的类型就是a1(实际对象)所属的类型,a1属于Jinmao,所以Jinmao就是d1的实际类型。

继续思考以上示例,我们增加Teddy类型,并实现Dog接口。当我们调用AddAge方法时,方法内部不用做任何修改。这就是接口给我们带来的好处。

接口类型

例3:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
go复制代码// 声明Dog接口,包含getAge方法
type Dog interface{
   getAge() int
}

type Jinmao struct {
   age int
}

func (j Jinmao) getAge() int {
   return j.age
}

func main()  {
   a1 := Jinmao{age:2}
   var d1 Dog= a1 // 声明Dog类型的实例d1,并把a1赋值给d
   fmt.Printf("d1的类型是:%T", d1) // d1的类型是:main.Jinmao
}

在fmt包的Printf方法中,%T代表d1的类型。最终输出d1的实际类型是Jinmao。

空接口

我们用interface{}表示空接口。根据Go语言中接口的定义,实现了接口方法的对象都是接口的实现类型,空接口没有定义方法,那也 就意味着所有类型都实现了空接口。

当方法可以接收任意类型的参数时,可以使用interface{}表示。

高级用法

接口类型

例4:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
go复制代码// 声明Dog接口,包含age方法
type Dog interface{
   getAge() int
}

type Jinmao struct {
   age int
}

func (j Jinmao) getAge() int {
   return j.age
}

func main()  {
   a1 := Jinmao{age:2}
   var d1 Dog= a1 // 声明Dog类型的实例d1,并把a1赋值给d
   v,ok := d1.(Jinmao) // 判断d1的实际类型是否是Jinmao
   fmt.Printf("v=%v,ok=%v",v,ok) // print v={2},ok=true
}

如上示例,我们通过v,ok := d1.(Jinmao)判断d1的实际类型。

一种更常见的方法是通过switch case语句判断数据的实际类型。

例5:

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
go复制代码// 声明Dog接口,包含getAge方法
type Dog interface{
   getAge() int
}

type Jinmao struct {
   age int
}

func (j Jinmao) getAge() int {
   return j.age
}

type Husky struct {
   age int
}

func (h Husky) getAge() int {
   return h.age
}

func assertType(i interface{}){
   switch i.(type) {
   case Jinmao:
      fmt.Println("实际类型是Jinmao")
   case Husky:
      fmt.Println("实际类型是Husky")
   case int:
      fmt.Println("实际类型是int")
   default:
      fmt.Println("未匹配到类型")
   }
}

func main()  {
   a1 := Jinmao{age:2}
   var d1 Dog= a1 // 声明Dog类型的实例d1,并把a1赋值给d

   assertType(a1) // print 实际类型是Jinmao
   assertType(d1) // print 实际类型是Jinmao
   assertType(100) // print 实际类型是int
   assertType("str") // print 未匹配到类型
}

在例5中我们可以看到,如何通过switch i.(type){}语句判断变量的实际类型。

值类型和指针类型方法

在讲方法的时候我们说到有值类型和指针类型的方法,这两种类型的方法在实现接口时有何不同呢?

例6:

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
go复制代码// 声明Dog接口,包含age方法
type Dog interface{
   getAge() int
}

type Jinmao struct {
   age int
}

func (j Jinmao) getAge() int {
   return j.age
}

type Husky struct {
   age int
}

func (h *Husky) getAge() int {
   return h.age
}

func main()  {
   a1 := Jinmao{age:2}
   var d1 Dog= a1 // 声明Dog类型的实例d1,并把a1赋值给d1

   a2 := Jinmao{age:3}
   var d2 Dog= &a2 // 声明Dog类型的实例d1,并把a1赋值给d2

   //a3 := Husky{age:2}
   //var d3 Dog= a3 // 声明Dog类型的实例d1,并把a1赋值给d3

   a4 := Husky{age:4}
   var d4 Dog= &a4 // 声明Dog类型的实例d1,并把a1赋值给d4

   fmt.Println(d1.getAge()) // print 2
   fmt.Println(d2.getAge()) // print 3
   // fmt.Println(d3.getAge())
   fmt.Println(d4.getAge()) // print 4
}

在上面的示例中,Jinmao的值类型实现了getAge(),a1是Jinmao的值类型,&a2是Jinmao的指针类型,a1和a2都可以赋值给Dog类型的值。Husky的指针类型实现了getAge(),a3是Husky的值类型,&a4是Husky的指针类型,此时只有&a4可以赋值Dog类型的d4。如果把a3赋值给d3会报编译时错误 Type does not implement 'Dog' as 'getAge' method has a pointer receiver。这是Go语言值类型方法和指针类型方法的特点,我们可以这样理解对象值类型的方法,也自动包含了指针类型的方法;而指针类型的方法不含值类型的方法。

对象值类型的方法,也自动包含了指针类型的方法是因为Go语言在编译期做了优化。

接口嵌套

接口之间可以通过嵌套实现类似于继承的效果。

例7:

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
go复制代码// 声明Dog接口,包含getAge方法
type Dog interface{
   getAge() int
}

// 声明Cat接口,包含getName方法
type Cat interface {
   getName() string
}
// Animal接口,嵌套了Dog和Cat接口
type Animal interface {
   Dog
   Cat
}

type Husky struct {
   age int
   name string
}

func (h Husky) getAge() int {
   println("I am Husky getAge!")
   return h.age
}

func (h Husky) getName() string {
   println("I am Husky getName!")
   return h.name
}

type OrangeCat struct {
   age int
   name string
}

func (o OrangeCat) getAge() int {
   println("I am OrangeCat getAge!")
   return o.age
}

func (o OrangeCat) getName() string {
   println("I am OrangeCat getName!")
   return o.name
}

func main()  {
   h1 := Husky{age:2,name:"xiaohei"}
   var a1 Animal= h1 // 声明Dog类型的实例d1,并把a1赋值给d1

   o1 := OrangeCat{age:1, name:"Tom"}
   var a2 Animal= o1 // 声明Dog类型的实例d1,并把a1赋值给d2

   fmt.Println(a1.getAge())
   fmt.Println(a1.getName())
   fmt.Println(a2.getAge())
   fmt.Println(a2.getName())
}

执行例6的代码,输出如下结果:

1
2
3
4
5
6
7
8
css复制代码I am Husky getAge!
2
I am Husky getName!
xiaohei
I am OrangeCat getAge!
0
I am OrangeCat getName!
Tom

在例6中我们声明Animal接口包含了Dog和Cat接口,要想实现Animal接口就需要实现Dog和Cat接口的所有方法。我们给Husky和OrangeCat结构体类型都实现了getAge和getName方法,所以a1和a2可以被h1和o1赋值。

接口赋值

接口赋值对于值类型和指针类型有所不同,请看下面的例子。

例8

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
go复制代码// 声明Dog接口,包含getAge方法
type Dog interface{
   getAge() int
}

type Jinmao struct {
   age int
}

func (j *Jinmao) setAge(i int) {
   j.age = i
}

func (j Jinmao) getAge() int {
   return j.age
}

func main()  {
   a1 := Jinmao{age:2}
   var d1 Dog= a1 // 声明Dog类型的实例d1,并把a1赋值给d1
   a1.setAge(20)
   fmt.Printf("a1.age=%d \n",a1.getAge())
   fmt.Printf("d1.age=%d \n",d1.getAge())

   a2 := Jinmao{age:3}
   var d2 Dog= &a2 // 声明Dog类型的实例d2,并把a2赋值给d2
   a2.setAge(30)
   fmt.Printf("a1.age=%d \n",a2.getAge())
   fmt.Printf("a1.age=%d \n",d2.getAge())
}

执行上面例8中的代码输出如下:

1
2
3
4
ini复制代码a1.age=20 
d1.age=2 
a1.age=30 
a1.age=30

通过结果我们可以看出,对a1调用setAge方法并没有改变d1的age;对a2调用setAge方法改变了d2的age。在Go语言中的赋值操作都是值拷贝, 但是d2拷贝的是a2的地址,所以d2和a2还是指向同一个内存地址,当a2的age改变时d2的age也改变了。

总结

本文我们主要介绍了Go语言接口的基本用法、嵌套使用、赋值原理等。接口在Go语言中极具特色,而且在面向对象编程中使用广泛。如果大家对文章内容有任何疑问或建议,欢迎私信交流。

我把Go语言基础知识相关文章的Demo都放到了下面这个仓库中,方便大家互相交流学习。 github.com/jialj/golan…

诚邀关注公众号:一行舟

每周更新技术文章,和大家一起进步。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

mysql-修改密码(error-1290 (HY000)

发表于 2021-11-23

)​

执行:flush privileges;

mysql 新设置用户或更改密码后需用flush privileges刷新MySQL的系统权限相关表,否则会出现拒绝访问或修改操作。

)​

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Python 操作腾讯云短信(sms)详细教程 腾讯云短信

发表于 2021-11-23

「这是我参与11月更文挑战的第23天,活动详情查看:2021最后一次更文挑战」

腾讯云短信

  1. 注册腾讯云

已有腾讯云账号可直接跳到第二步

  • 官网
  • 注册, 微信扫码关注腾讯云助手即可快速注册
    在这里插入图片描述
  • 选择 注册新账号,注册完成后,实名认证一下。选择 个人认证,填写一下信息就ok了。
    在这里插入图片描述)在这里插入图片描述)在这里插入图片描述
  1. 开通腾讯云短信

  • 通过上一步我们已经注册好了腾讯云账号,接下来去开通 腾讯云短信 功能。
  • 开通网址:console.cloud.tencent.com/smsv2

2.1 创建应用

  • 创建一个应用,点进去就会看到 SDK AppID & App Key,后面会用得到。
    在这里插入图片描述)在这里插入图片描述

2.2 创建签名

  • 我们创建一个国内短信的签名
    在这里插入图片描述
  • 签名类型我推荐使用 公众号 ,比较容易通过,个人公众号也比较容易申请。
    在这里插入图片描述

2.3 创建模板

  • 接下来我们创建几个短信模板,注册、登录等等
    在这里插入图片描述
  • 这里有一些常用的标准模板,直接使用即可
    在这里插入图片描述

注意:
发送短信有频率的限制,不过这个只有企业用户可以修改,个人用户无法修改
在这里插入图片描述

  1. Python 操作腾讯云短信

  • 官方SDK文档

3.1 模块安装

1
python复制代码pip install qcloudsms_py

3.2 详细代码【可直接作为接口】

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
44
45
46
47
48
49
50
51
python复制代码# -*- coding: UTF-8 -*-
'''=================================================
@Project -> File :MyDjango -> sms
@IDE :PyCharm
@Author :ruochen
@Date :2020/6/21 15:57
@Desc :
=================================================='''

import ssl

# ssl._create_default_https_context = ssl._create_unverified_context
from qcloudsms_py import SmsMultiSender, SmsSingleSender
from qcloudsms_py.httpclient import HTTPError
from django.conf import settings

def send_sms_single(phone_num, template_id, template_param_list):
"""
单条发送短信
:param phone_num: 手机号
:param template_id: 腾讯云短信模板ID
:param template_param_list: 短信模板所需参数列表,例如:【验证码:{1},描述:{2}】,则传递参数 [888,666]按顺序去格式化模板
:return:
"""
appid = settings.TENCENT_SMS_APP_ID # 自己应用ID
appkey = settings.TENCENT_SMS_APP_KEY # 自己应用Key
sms_sign = settings.TENCENT_SMS_SIGN # 自己腾讯云创建签名时填写的签名内容(使用公众号的话这个值一般是公众号全称或简称)
sender = SmsSingleSender(appid, appkey)
try:
response = sender.send_with_param(86, phone_num, template_id, template_param_list, sign=sms_sign)
except HTTPError as e:
response = {'result': 1000, 'errmsg': "网络异常发送失败"}
return response

def send_sms_multi(phone_num_list, template_id, param_list):
"""
批量发送短信
:param phone_num_list:手机号列表
:param template_id:腾讯云短信模板ID
:param param_list:短信模板所需参数列表,例如:【验证码:{1},描述:{2}】,则传递参数 [888,666]按顺序去格式化模板
:return:
"""
appid = settings.TENCENT_SMS_APP_ID # 自己应用ID
appkey = settings.TENCENT_SMS_APP_KEY # 自己应用Key
sms_sign = settings.TENCENT_SMS_SIGN # 自己腾讯云创建签名时填写的签名内容(使用公众号的话这个值一般是公众号全称或简称)
sender = SmsMultiSender(appid, appkey)
try:
response = sender.send_with_param(86, phone_num_list, template_id, param_list, sign=sms_sign)
except HTTPError as e:
response = {'result': 1000, 'errmsg': "网络异常发送失败"}
return response

最后,欢迎大家关注我的个人微信公众号 『小小猿若尘』,获取更多IT技术、干货知识、热点资讯

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

代理模式的新花样,istio秀肌肉!

发表于 2021-11-23

原创:小姐姐味道(微信公众号ID:xjjdog),欢迎分享,转载请保留出处。

紧紧抓住最新技术的脉搏,用人话普及前沿技术,是xjjdog的一贯作风,现在也是一种责任和习惯。从漫天飞舞的华丽辞藻中,抓住技术的本质,可以避免喧宾夺主,也可以避免被忽悠。

基础架构,每一波都在革自己的命。ServiceMesh是处于云原生时代的改革尖兵,其中istio以其高贵的身份与绝佳的性能,一骑绝尘,隐隐有成为标准的可能。

接下来,让我们抽丝剥茧,来看一下istio到底是个啥。

  1. 中间层是基础架构的绝招

在介绍istio到底为何物的时候,我们先来描述一个问题。

假如你的公司,打算全面拥抱SpringCloud微服务,那么你一定会选择Java语言。然后一些比较古老的项目,都往上面靠。

但总有一些比较顽固的项目,无法用Java重构。比如,你的某个工程是使用C++写的,重构的成本非常大。那怎么办呢?

1.1 sidecar

软件行业有一个永恒不变的真理:如果你想要快速有效的解决两个组件之间的问题,那就加入一个中间层。

无论是概念上的“中台”,还是技术层面的“proxy”,甚至是啰里啰唆的DDD分层,都可以这么玩!

对于网络请求来说,Proxy可以接管所有的流量,然后对这些流量进行转化、分析、调度,就能够把一个丑八怪服务套一层皮,变成一个外观上漂漂亮亮的服务。

解决上面的问题,我们只需要使用SpringCloud兼容的技术开发这个Proxy,然后做一层转化,将请求转化成C++调用,就可以很好的完成工作。调用这个Proxy的请求,压根不会想到后面竟然是C++。

image-20211118162152313.png

通常情况下,我们需要把Proxy和C++服务部署在一台机器上,它们就会有比较好的性能表现;当然,现在容器搞起来之后,就可以运行在两个不同的Docker实例上;再后来,k8s来了,一个Pod里可以直接放上Proxy和C++两个容器实例,它们之间就可以通过localhost通信,成为了一个整体。

这就是sidecar的概念。对这一部分不是很熟悉的同学,可以看以前的一篇文章:《ServiceMesh的关键:边车模式(sidecar);又要开车了》。

为什么一定要讲到代理呢?因为istio核心组件,本质上,就是一个sidecar形式的proxy。你要请求后端的服务,就一定要经过istio。它接管了所有的流量,并根据这些流量的属性进行了划分。

image-20211118162827931.png

1.2 proxy的功能

理念就是这么简单。但一旦把微服务里各种乱七八糟的功能,全部交给istio去做,那事情就变得复杂起来。

众所周知,微服务所引入的问题,比它解决的问题还要多。有了配套的设施,微服务才算是真正的微服务。

我们来看一个请求从外围进入微服务后,都要进行哪些动作。

  • 微服务要想被找到彼此,就需要有一个统一的注册中心,当然它本质上是一个配置中心
  • 想要跟踪一个请求在不同运行实例上的运行情况,就需要一个集中的Tracing系统
  • 请求通过RPC调用后端的服务,首先要经过负载均衡,还要熔断、限流、重试、故障转移等。当然一切的基础,需要在安全的前提下进行
  • 一个请求还有更多属性,比如鉴权、加密、认证,灰度等。如果每个功能都做一遍,又复杂又枯燥。
  • 配套的CI/CD等,加快研发流程

关键是,这些功能对于业务开发的同学来说,并没有什么用。业务的同学只需要关注自己的业务逻辑就可以了,但目前我们上面所提到的各种技术术语和功能组件,现阶段的业务开发一点都绕不开。

更要命的是,不论我升级上面的哪一个组件,都需要业务重新引入一个新的SDK版本,然后滚动进行升级。因为它们之所以称之为基础设施,就是因为牵一发而动全身的。

所以我们的部署模式要有一些改变。如果我们的所有应用,都不直接向外提供服务,而是全部通过代理去转发请求,那么它就会变成下面这张图的样子。

image-20211118155953461.png

Envoy Proxy就相当于我们的代理sidecar,而Service A和Service B,就代表我们真正的微服务。服务实例和代理的数量,是1:1的。

那么我们上面所提到的所有流量,都可以由Envoy来接管—它和你直接调用后面的Service是没什么两样的,但由于它是一个Proxy,那就可以像Aop一样,在外面包一层,干任何事!

所有神奇的事情,都可以在代理发生!

1.3 数据平面和控制平面

如果你以前接触过ServiceMesh,你一定听说过这两个词。

数据平面,其实指的就是我们的Proxy集合。它虽然是个网状,但如果我们把它的概念统一一下,它就叫平面。

那么控制平面,就是配套的控制管理台,以及下发指令的管理后台等。

也就是说,大多数情况下,仅仅数据平面,服务就能运行。但要看框架强不强,还得看控制平面易用不易用。

image-20211118164753062.png

图中的上半部分,就是传说中的数据平面。经过我们上面的介绍,应该对其非常熟了。istio把它搞复杂的地方在于,它加入了证书体系来控制认证和授权。

但如果你的公司是自研ServiceMesh的话,那工作其实就简单的多了。你的工作量全部集中在proxy的开发上。

控制平面,如果不按照istio的规则来,其实也是可有可无的。比如,你的日志收集,可以直接穿透Pod连接到你的ELKB平台,遥测这一环,就可以沿用你公司原来的技术架构。

所以说。

如果你的公司比较新,基础设施还没有完善,那么直接上istio,那是非常棒的,因为大部分基础架构的组件,它都替你做了。

但如果你的公司有些年月,基础设施都开发的差不多了,那切换到istio的基础设施,那就是闲的!你的最好选择,就是开发这样一个代理,然后把流量导到自己的基础组件上。

使用C++或者Go语言而不是Java开发这样的组件,是最好的选择。因为Java开发Agent往往会占用比较多的内存。当然,如果内存对你来说不值钱的话,这话当我没说。

  1. 统一是基础架构的首要目标

因为基础架构的范围很大,所以每一种技术都有多种解决方案。比如MQ,比较流行的就有Kafka、RabbitMQ、Pulsar等。

对于社区来说,竞争和差异化是促进创新的原则。但对于一个公司来说,基础架构的统一才是首要目标(那些巨无霸另说)。

所以无论是调度也好,还是流量拦截也罢,这个代理的职责只会变得越来越大。如果技术不统一,那么功能就会成为笛卡尔乘积式的爆炸。istio选用了比较主流的方式,代理通过Envoy来实现,而中间的网络协议,则支持 HTTP/1.1,HTTP/2,gRPC 或者 TCP等主流的通信协议。

统一,是它在这里体现的价值。

在功能上,Pod天然能够将代理和服务绑在一块,所以k8s成了首选的调度平台。当然你非要做类似NodePort一样的机器代理,那也没什么本质的区别。

1__pYrG7dF5AP9eHCa4YuTFgw.png

最终,经过社区的融合(或者说垄断),istio成为了完成统一整个目标的框架。它的职责也越来越多,慢慢演变成了上图的模样。

上半部分依然是雷打不动的数据平面,但istio在控制平面上开了花。

控制中心做了进一步的细分,分成了 Pilot、Mixer、和 Citadel,它们的各自功能如下:

  • Mixer:为整个集群执行访问控制和 策略管理(限流、限额等),并收集代理所观察到的,服务之间的流量统计数据,也就是遥测(监控、APM等)数据。
  • Pilot:为 Envoy 提供了服务发现,流量管理和智能路由(AB测试、金丝雀发布等),以及错误处理(超时、重试、熔断)功能。
  • Citadel:为服务之间提供认证和证书管理,可以让服务自动升级成 TLS 协议。
  • Galley:这个组件并不向数据平面直接提供业务能力,而是作为其他控制平面的协调组件,这样解耦的更加彻底,核心功能也越来越多。

End

我们可以到,一个正常的请求,在加入istio之后,就全部变成了proxy和proxy之间的通讯。proxy既代理了入流量,也代理了出流量,所有的流量都从它这里转了一圈,所以它能够拦截和分析任何数据。借助于k8s的助力,服务和proxy之间可以通过localhost通信。

对于微服务架构来说,一个请求会分散到多台机器上,耗时、错误日志等都会变的分散,问题排查起来不得不依赖APM等组件。istio更加彻底,你根本不知道具体的服务节点到底被调度到哪里了,传统的人肉运维方式失效,对上游的工具依赖性更高。

使用istio有很多前置的知识点需要啃掉,比如虚拟化、k8s。新公司直接跳过这两者拥抱istio甚至是更好的选择。但如果你的公司已经有了非常好用的基础设置工具,又与社区不兼容的话,那恐怕只有借鉴理念,自研proxy了。

作者简介:小姐姐味道 (xjjdog),一个不允许程序员走弯路的公众号。聚焦基础架构和Linux。十年架构,日百亿流量,与你探讨高并发世界,给你不一样的味道。我的个人微信xjjdog0,欢迎添加好友,进一步交流。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Python核心数据结构时间复杂度

发表于 2021-11-23

为什么?

对于编程来说,选择正确的数据结构是至关重要的。

特别是,如果算法是计算密集型的,例如训练机器学习模型的算法或处理大数据的算法,那么认真仔细的选择合适的数据结构是必要前提工作。如果使用了不合适的数据结构,最终会严重影响应用程序的性能。

孙子兵法: “先胜而后战,先战而后败”! 思考如何组织数据,已经立于编程不败之地了!

所以接下来,给大家分享下如何评估时间复杂度以及Python核心数据结构的复杂度。

本文解释了 Python 中数据结构关键操作的 Big-O 表示法。 Big-O 表示法是一种测量操作时间复杂度的方法。

1

什么是Big-O表示法?

在一个算法中会执行许多操作。这些操作包括迭代集合,复制元素或整个集合,将元素附加到集合,在集合的开头或结尾插入元素,删除元素或更新集合中的元素。

Big-O 用来衡量操作的时间复杂度。它测量算法运行需要花费的时间。同时也可以测量空间复杂度(算法占用空间),但本文将重点关注时间复杂度。

简单地说,Big-O是一种基于输入大小(nnn)来衡量操作性能的表示方法。

有哪些不同的Big-O符号?

假设程序输入的大小为 nnn ,让我们来看下常见的Big-O符号。

  • O(1)O(1)O(1): 无论输入的 nnn 有多大,执行操作所需的时间恒定。这是常数复杂度,最快的算法。例如,检查集合中是否存在元素,时间复杂度是 O(1)O(1)O(1)。
  • O(logn)O(log n)O(logn): 随着输入的增加,执行操作所需的时间呈对数增加。这是对数时间复杂度。一般优化后搜索算法是O(logn)O(log n)O(logn),比如二分查找。
  • O(n)O(n)O(n): 随着输入的增加,执行操作所需的时间呈线性增加。这是线性时间复杂度。就性能而言,属于中等。 例如,如果想对一个集合中的所有元素求和,那么必须遍历该集合中的全部元素。 因此时间复杂度是O(n)O(n)O(n)。
  • O(nlogn)O(nlog n)O(nlogn): 快速排序算法的时间复杂度通常为O(nlogn)O(nlog n)O(nlogn)。
  • O(n2)O(n^2)O(n2): 平方时间度复杂度,如两层嵌套的循环。
  • O(n!)O(n!)O(n!): 阶乘时间复杂度,它非常缓慢。

下图提供了时间复杂度的概述。 O(1)O(1)O(1)是最快的,O(n2)O(n^2)O(n2)是慢的,O(n!)O(n!)O(n!)是最慢的!

Big-O 表示法是独立于机器的,忽略系数的,被数学家、技术专家、数据科学家广泛使用。

最好的、平均的、最坏的情况

当我们计算一个操作的时间复杂度时,可以根据最好的、平均的或最坏的情况产生复杂度。

  1. 最佳情况: 顾名思义,这些是数据结构和集合中的元素以及参数处于最佳状态时的情况。例如,假设要在集合中查找元素,而该元素恰好是集合的第一个元素,那么它是该操作的最佳情况。
  2. 平均情况: 是根据输入值的分布来定义复杂性。
  3. 最坏情况: 可能是这样一种操作,它需要在大型集合(例如列表)中查找元素,而这个元素是最后一个元素,需要遍历集合的所有元素才能找到。

Python 数据结构 和 时间复杂度

接下来就是重点!!!分享 Python 中的常见数据结构,并介绍它们的时间复杂度。注意,我们专注于平均情况。

列表

列表是迄今为止 Python 中最重要的数据结构之一。可以将列表用作堆栈(添加的最后一项是第一个输出)或队列(添加的第一个项目是第一个输出)。列表是有序且可变的集合,而且可以随意更新列表。

列表常见操作的时间复杂度:

  • 插入:O(1)O(1)O(1)
  • 获取:O(n)O(n)O(n)
  • 删除:O(n)O(n)O(n)
  • 迭代:O(n)O(n)O(n)
  • 获取长度:O(1)O(1)O(1)

集合

集合也是 Python 中使用最广泛的数据集合之一。python集合本质上是一个无序的集合。集合不允许重复,因此集合中的每个项目都是唯一的。集合支持并集、差集、集合的交集等多种数学运算。

集合常见操作的时间复杂度:

  • 集合是否有元素:O(1)O(1)O(1)
  • 求集合B与A的差:O(nA)O(n_A)O(nA​)
  • 集合A和B的交集:O(min(nA,nB))O(min(n_A, n_B))O(min(nA​,nB​))
  • 集合A和B的并集:O(nA+nB)O(n_A+n_B)O(nA​+nB​)

字典

最后,分享下字典这一数据结构。字典是一个键值对集合。键在字典中是唯一的,以防止项目冲突。这是一个非常有用的特性。

字典由键索引,其中键可以是字符串、数字甚至是带有字符串、数字或元组的元组。

我们可以对字典执行许多操作,例如存储键的值,或基于键检索项目,或遍历元素等。

字典常见操作的时间复杂度:

  • 获取元素:O(1)O(1)O(1)
  • 更新元素:O(1)O(1)O(1)
  • 删除元素:O(1)O(1)O(1)
  • 遍历字典:O(n)O(n)O(n)

小节

本文介绍了 Python 中常见数据结构相关操作复杂的的Big-O表示法。Big-O表示法本质上是一种衡量操作时间复杂度的方法。

希望通过上面的分享,将来你在编程或工作时,面对问题可以正确的评估选择合适的数据结构,从而设计一个完美、健壮的程序!

上面只是Python数据结构复杂度的冰山一角,感兴趣的同学可以点击python数据结构与算法,总共88个小节,592张精美插图,系统介绍数据结构及其Python编码实现。

如果觉得还可以,点赞收藏,好人一生平安 。

pythontip 出品,Happy Coding!

原文链接,代码中在线运行

公众号: 夸克编程

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

云原生时代:看 Apache APISIX 如何玩转可观测性

发表于 2021-11-23

可观测性是从系统外部去观察系统内部程序的的运行时状态和资源使用情况。衡量可观测性的主要手段包括:Metrics、Logging 和 Tracing,下图是 Metrics、Logging 和 Tracing 之间的关系。

图片

举个例子,Tracing 和 Logging 重合的部分代表的是 Tracing 在 request 级别产生的日志,并通过 Tracing ID 将 Tracing 和 Logging 关联起来。对这份日志进行一定的聚合运算之后,能够得到一些 Metrics。Tracing 自身也会产生一些 Metrics,例如调用量之间的关系。

Apache APISIX 的可观测性能力

Apache APISIX 拥有完善的可观测性能力:支持 Tracing 和 Metrics、拥有丰富的 Logging 插件生态、支持查询节点状态。

Tracing

Apache APISIX 支持多种 Tracing 插件,包括:Zipkin、OpenTracing 和 SkyWalking。需要注意是:Tracing 插件默认处于关闭状态,使用前需要手动开启 Tracing 插件;Tracing 插件需要与路由或全局规则绑定,如果没有采样率的要求,建议与全局规则绑定,这样可以避免遗漏。

Metrics

在 Apache APISIX 中, Metrics 的相关信息通过 Prometheus Exporter上报,兼容 Prometheus 的数据格式。在 Apache APISIX 中使用 Prometheus Plugin 有两件事情需要注意。

第一,请尽量提高路由、服务和上游这三者名称的可读性。

Prometheus Plugin 中有一个名为 prefer_name 的参数,将这个参数的值设置为 true 时,即:prefer_name: true,如果路由、服务和上游这三者的名称可读性比较强,这会带来一些好处:后续通过 Grafana 监控大屏展示参数的时候,不仅能够清楚地展示出所有的数据,还能够清晰地知晓这些数据的来源。如果prefer_name 参数的值为 false,则只会展示资源的 ID 作为数据来源,例如 路由 ID、上游 ID,进而造成监控大屏的可读性较低的问题。

第二,Prometheus Plugin 必须与路由或者全局规则绑定,然后才可以查看到指定资源的 Metrics。

完成上述设置以后,Metrics 的数据会存储在 Prometheus 里面。由于 Prometheus 的存储性能很好,但展示性能欠佳,所以我们需要借助 Grafana Dashboard 展示数据。我们可以看到 Nginx 实例的 Metrics、网络带宽的 Metrics、路由和上游的 Metrics 等,详情如下图所示:

图片

Logging

Apache APISIX 支持多种日志插件,可以与其他外部的平台直接分享日志数据。Error Log 插件支持 HTTP 与 TCP 协议,并且兼容 SkyWalking 的日志格式。也可以通过 FluentBit 等日志收集组件,将日志同步到日志平台进行处理。

Access Log 插件目前还不支持在日志格式里面进行嵌套。因为 Access Log 插件是路由级别的,所以需要跟路由进行绑定,才可以收集到路由的访问日志。但是日志的格式是全局的,而全局只能有一份日志格式。

支持查询节点状态

Apache APISIX 的支持查询节点状态,启用之后,可以通过 /apisix/status 收集到节点的信息,包括节点数、等待链接数、处理连接数等。

图片

美中不足

上文讲到,Apache APISIX 的可观测性能力非常完善,能够收集 Metrics、Logging 和 Tracing 等信息。虽然借助 Apache APISIX 的内置插件配合 Grafana Dashboard,能够解决监控数据收集和指标可视化问题,但是各种数据分散在各个平台。期望有一个可观测性分析平台能集成 Metrics、Logging、Tracing 信息,能够将所有数据联动起来。

使用 Apache SkyWalking 增强 Apache APISIX 的观测能力

Apache SkyWalking 是一个针对分布式系统的应用性能监控(APM)和可观测性分析平台。它提供了多维度应用性能分析手段,从分布式拓扑图到应用性能指标、Trace、日志的关联分析与告警。

图片

一站式数据处理

Apache SkyWalking 支持对接 Metrics、Logging、Tracing 等多种监控数据,兼容 Prometheus 数据模型,还可以通过 Log Analysis Language 进行二次聚合,产生新的 Metrics。

更详细的数据展示

Apache SkyWalking 的 Dashboard 分为上下两个区域。上部是功能选择区域,下部是面板内容。Dashboard 提供全局、服务、示例、Endpoint 等多个实体维度的 Metrics 相关信息,支持以不同的视图展示可观测性。以全局视图为例,展示的 Metrics 包括:服务负载、慢服务数量、不健康的服务数量等,如下图所示。

图片

另外值得一说的是 SkyWalking Dashboard 的 Trace 视图。SkyWalking 提供了 3 种展现形式:列表、树状图和表格。Trace 视图是分布式追踪的典型视图,这些视图允许用户从不同角度查看追踪数据,特别是 Span 间的耗时关系。

SkyWalking Dashboard 也支持拓扑图。拓扑图是根据探针上行数据分析出的整体拓扑结构。拓扑图支持点击展现和下钻单个服务的性能统计、Tracing、告警,也可以点击拓扑图中的关系线,展示服务之间、服务示例间的性能 Metrics。

支持容器化部署

Kubernetes 是一个开源的云原生容器化集群管理平台,目标是让部署容器化的应用简单且高效。Apache SkyWalking 后台可以部署在 Kubernetes 之中,而且得益于 Kubernetes 的高效率管理,可以保证 UI 组件的高可用性。

如果在集群上部署了 Apache APISIX,Apache SkyWalking 支持以 sidecar 或服务发现的形式部署 SkyWalking Satellite,监控集群中的 Apache APISIX。

未来计划

Apache APISIX 在未来仍会继续加强可观测性相关的功能支持,例如:

  1. 解决 SkyWalking Nginx-Lua 插件的 peer 缺失问题
  2. 支持在日志中打印 trace id
  3. 接入访问日志
  4. 支持网关元数据

结语

本文介绍了 Apache APISIX 的可观测性能力以及如何通过 Apache SkyWalking 提升Apache APISIX 的可观测性能力。未来两个社区还会继续深度合作,进一步增强 Apache APISIX 的可观测性能力。希望大家能够多多地参与到 Apache APISIX 和 Apache SkyWalking 项目中来。如果你对这两个开源项目很感兴趣,却不熟悉代码,写文章、做视频、对外分享、积极参与社区和邮件列表讨论都是很不错方式。

活动预告

Apache APISIX 大咖面对面第一期举办后,小伙伴们在评论区直呼收获满满、不过瘾、期待下期!

11 月 30 日 19:30 第二期大咖面对面如约而至,本期嘉宾邀请到了融云联合创始人兼 CTO - 杨攀、Kyligence 联合创始人兼首席架构师 - 史少锋、KubeSphere 创始人 - 周小四、Apache APISIX Committer - 王晔倞,众咖齐聚一起畅谈面对职场变化和转型分岔路,技术人投身 To B 领域的问题与挑战。

  • 中国的 To B 企业为什么发展很难?
  • 技术人投身 To B 领域成功的原因有哪些、转型失败的原因又有哪些?
  • “年纪轻了做技术,年纪大了转管理”这是不是唯一的途径?职业发展还有哪些途径?
  • 开源基础软件 To B 商业化和传统基础软件 To B 商业化,运营方式有哪些不同?

图片

11 月 30 日 19:30 锁定「Apache APISIX 视频号」,来和嘉宾互动提问,直播间还有精美礼品等你哦。

入群交流

扫描下方二维码,或在公众号后台回复【直播交流群】,加入 Apache APISIX 线上直播交流群,了解更多社区动态!

图片

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

SpringCloud09——Gateway服务网关

发表于 2021-11-23

一、概述

1、文档

  • zuul:github.com/Netflix/zuu…
  • Gateway:cloud.spring.io/spring-clou…

zuul现在已经不用了,不用学了😀😀😀

2、Gateway概述

SpringCloud Gateway是基于WebFlux框架实现的,而WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty。

Cloud全家桶中有个很重要的组件就是网关,在1.x版本中都是采用的Zuul网关;

但在2.x版本中,zuul的升级一直跳票,SpringCloud最后自己研发了一个网关替代Zuul,那就是SpringCloud Gateway—句话:gateway是原zuul1.x版的替代

img
Gateway是在Spring生态系统之上构建的API网关服务,基于Spring 5,Spring Boot 2和Project Reactor等技术。

Gateway旨在提供一种简单而有效的方式来对API进行路由,以及提供一些强大的过滤器功能,例如:熔断、限流、重试等。

SpringCloud Gateway是Spring Cloud的一个全新项目,基于Spring 5.0+Spring Boot 2.0和Project Reactor等技术开发的网关,它旨在为微服务架构提供—种简单有效的统一的API路由管理方式。

SpringCloud Gateway作为Spring Cloud 生态系统中的网关,目标是替代Zuul,在Spring Cloud 2.0以上版本中,没有对新版本的Zul 2.0以上最新高性能版本进行集成,仍然还是使用的Zuul 1.x非Reactor模式的老版本。而为了提升网关的性能,SpringCloud Gateway是基于WebFlux框架实现的,而WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty。

Spring Cloud Gateway的目标提供统一的路由方式且基于 Filter链的方式提供了网关基本的功能,例如:安全,监控/指标,和限流。

3、为什么选择Gateway?

Gateway的特性

  • 基于Spring Framework 5,Project Reactor和Spring Boot 2.0进行构建;
  • 动态路由:能够匹配任何请求属性;
  • 可以对路由指定Predicate (断言)和Filter(过滤器);
  • 集成Hystrix的断路器功能;
  • 集成Spring Cloud 服务发现功能;
  • 易于编写的Predicate (断言)和Filter (过滤器);
  • 请求限流功能;
  • 支持路径重写。

SpringCloud Gateway与Zuul的区别

  1. 在SpringCloud Finchley正式版之前,Spring Cloud推荐的网关是Netflix提供的Zuul。
  2. Zuul 1.x,是一个基于阻塞I/O的API Gateway。
  3. Zuul 1.x基于Servlet 2.5使用阻塞架构它不支持任何长连接(如WebSocket)Zuul的设计模式和Nginx较像,每次I/О操作都是从工作线程中选择一个执行,请求线程被阻塞到工作线程完成,但是差别是Nginx用C++实现,Zuul用Java实现,而JVM本身会有第-次加载较慢的情况,使得Zuul的性能相对较差。
  4. Zuul 2.x理念更先进,想基于Netty非阻塞和支持长连接,但SpringCloud目前还没有整合。Zuul .x的性能较Zuul 1.x有较大提升。在性能方面,根据官方提供的基准测试,Spring Cloud Gateway的RPS(每秒请求数)是Zuul的1.6倍。
  5. Spring Cloud Gateway建立在Spring Framework 5、Project Reactor和Spring Boot2之上,使用非阻塞API。
  6. Spring Cloud Gateway还支持WebSocket,并且与Spring紧密集成拥有更好的开发体验

二、三大核心概念

1、三大概念

  • Route(路由) : 路由是构建网关的基本模块,它由ID,目标URI,一系列的断言和过滤器组成,如断言为true则匹配该路由;
  • Predicate(断言) : 参考的是Java8的java.util.function.Predicate,开发人员可以匹配HTTP请求中的所有内容(例如请求头或请求参数),如果请求与断言相匹配则进行路由;
  • Filter(过滤) :指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前或者之后对请求进行修改。

img
img

2、Gateway的工作流程

image-20211122164026753
客户端向Spring Cloud Gateway发出请求。然后在Gateway Handler Mapping 中找到与请求相匹配的路由,将其发送到GatewayWeb Handler。

Handler再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。

过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前(“pre”)或之后(“post”)执行业务逻辑。

三、Gateway配置

1、环境

新建模块:cloud-gateway-gateway9527

pom:

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
44
45
46
47
48
49
50
xml复制代码<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>cloud2021</artifactId>
<groupId>com.atguigu</groupId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<groupId>com.atguigu.springcloud</groupId>
<artifactId>cloud-gateway-gateway9527</artifactId>

<dependencies>
<!--gateway-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--eureka-client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
<dependency>
<groupId>com.atguigu.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<!--一般基础配置类-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

yaml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
yaml复制代码server:
port: 9527

spring:
application:
name: cloud-gateway

eureka:
instance:
hostname: cloud-gateway-service
client: #服务提供者provider注册进eureka服务列表内
service-url:
register-with-eureka: true
fetch-registry: true
defaultZone: http://eureka7001.com:7001/eureka

主启动类:

1
2
3
4
5
6
7
8
java复制代码@SpringBootApplication
@EnableEurekaClient
public class GateWayMain9527
{
public static void main(String[] args) {
SpringApplication.run(GateWayMain9527.class, args);
}
}

业务类:无

2、gateway配置

为了保证可以路由跳转,加上如下代码:

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
yaml复制代码server:
port: 9527

spring:
application:
name: cloud-gateway
#############################新增网关配置###########################
cloud:
gateway:
routes:
- id: payment_routh #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: http://localhost:8001 #匹配后提供服务的路由地址
#uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/payment/get/** # 断言,路径相匹配的进行路由

- id: payment_routh2 #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: http://localhost:8001 #匹配后提供服务的路由地址
#uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/payment/lb/** # 断言,路径相匹配的进行路由
####################################################################

eureka:
instance:
hostname: cloud-gateway-service
client: #服务提供者provider注册进eureka服务列表内
service-url:
register-with-eureka: true
fetch-registry: true
defaultZone: http://eureka7001.com:7001/eureka

启动测试。

image-20211122170333854

这回访问http://localhost:9527/payment/get/31就可以了。

3、编码方式配置

上面采用yaml方式配置,现在可以使用编码方式配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
java复制代码@Configuration
public class GateWayConfig
{
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder)
{
RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();

routes.route("path_route_atguigu",
r -> r.path("/guonei")
.uri("http://news.baidu.com/guonei")).build();

return routes.build();
}
}

直接访问http://localhost:9527/guonei

四、根据微服务名实现动态路由

这里要模拟使用gateway的方式实现动态路由,而不是ribbon了。

9527模块的yaml文件修改成这样:

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
yaml复制代码server:
port: 9527

spring:
application:
name: cloud-gateway
#############################新增网关配置###########################
cloud:
gateway:
discovery:
locator:
enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由
routes:
- id: payment_routh #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/payment/get/** # 断言,路径相匹配的进行路由

- id: payment_routh2 #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/payment/lb/** # 断言,路径相匹配的进行路由
####################################################################

eureka:
instance:
hostname: cloud-gateway-service
client: #服务提供者provider注册进eureka服务列表内
service-url:
register-with-eureka: true
fetch-registry: true
defaultZone: http://eureka7001.com:7001/eureka

启动测试:localhost:9527/payment/lb,发现确实实现了轮询操作

五、Predicate的使用

9527启动后,发现后台出现了这些

image-20211123103322685

进入gateway官网(上面有),发现一共有11种Predicate

image-20211123103635467

Predicate是什么

Spring Cloud Gateway将路由匹配作为Spring WebFlux HandlerMapping基础架构的一部分。

Spring Cloud Gateway包括许多内置的Route Predicate工厂。所有这些Predicate都与HTTP请求的不同属性匹配。多个Route Predicate工厂可以进行组合。

Spring Cloud Gateway创建Route 对象时,使用RoutePredicateFactory 创建 Predicate对象,Predicate 对象可以赋值给Route。Spring Cloud Gateway包含许多内置的Route Predicate Factories。
所有这些谓词都匹配HTTP请求的不同属性。多种谓词工厂可以组合,并通过逻辑and。

After Route Predicate Factory的使用

按照官网的说法:

image-20211123104041265
在yaml文件底下加上一行即可,但是要修改成中国时间:

测试,发现这样可以获取到上面格式的时间:

1
2
3
4
5
6
7
8
9
java复制代码public class T2 {
public static void main(String[] args) {
ZonedDateTime zbj = ZonedDateTime.now();
System.out.println(zbj);
}
}

//输出
//2021-11-23T10:44:35.485+08:00[Asia/Shanghai]

那么修改一下:

1
2
3
4
5
6
7
yaml复制代码routes:
- id: payment_routh #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/payment/lb/**
- After=2021-11-23T10:44:35.485+08:00[Asia/Shanghai]

然后这样就代表上面这个路由只有在某某时间之后才能使用,现在把上面after的时间修改一下,再启动测试发现报错了,说明确实生效了,这种有点类似秒杀场景、或者项目定时上线都可以采用这种方式。

image-20211123105133793

Cookie Route Predicate的使用

就是要求带不带cookie,或者带哪种cookie。

Cookie Route Predicate需要两个参数,一个是cookie name,一个是正则表达式。路由的规则会通过正则表达式与cookie进行匹配。

1
2
3
4
5
6
7
yaml复制代码- id: payment_routh2 #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/payment/lb/** # 断言,路径相匹配的进行路由
- After=2021-11-23T10:44:35.485+08:00[Asia/Shanghai]
- Cookie=username, James

这里采用curl的方式发送请求:

image-20211123110501832
image-20211123110955737
其他的按照官网去写即可。

六、Filter的使用

路由过滤器可用于修改进入的HTTP请求和返回的HTTP响应,路由过滤器只能指定路由进行使用。Spring Cloud Gateway内置了多种路由过滤器,他们都由GatewayFilter的工厂类来产生。

过滤器一般只有两种:

  • GatewayFilter
  • GlobalFilter

自定义全局过滤器

过滤器太多了,要学也很麻烦,这里实现自定义过滤器,举个例子同时也更加通用,需要的话自己配即可:

创建个包filter,然后创建filter的类,需要实现两个接口:GlobalFilter、Ordered:

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
java复制代码@Component
@Slf4j
public class MyLogGateWayFilter implements GlobalFilter,Ordered
{

@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain)
{
log.info("***********come in MyLogGateWayFilter: "+new Date());

String uname = exchange.getRequest().getQueryParams().getFirst("uname");

if(uname == null)
{
log.info("*******用户名为null,非法用户,o(╥﹏╥)o");
exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
return exchange.getResponse().setComplete();
}

return chain.filter(exchange);
}

@Override
public int getOrder()
{
return 0;
}
}

测试:localhost:9527/payment/lb?uname=aa可以访问,不带uname不能访问。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

1…220221222…956

开发者博客

9558 日志
1953 标签
RSS
© 2025 开发者博客
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%