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

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


  • 首页

  • 归档

  • 搜索

Spring Boot 微框架使用

发表于 2021-11-14
  1. springboot的引言

Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化Spring应用的 初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而使开发人员不 再需要定义样板化的配置。通过这种方式,Spring Boot致力于在蓬勃发展的快速应 用开发领域(rapid application development)成为领导者。

springboot(微框架) = springmvc(控制器) + spring(项目管理)


  1. springboot的特点

1.微框架,与spring4一起诞生,如@RestController 2.springboot可以创建独立运行的应用而不依赖于容器 3.内嵌Tomcat,无需部署WAR文件 4.提供maven极简配置,缺点是会引入很多你不需要的包 5.根据项目来依赖,从而自动配置spring,需要什么配置什么,做到了可控 6.提供了监控、应用的健康等功能 7.简化配置,不需要配置过多的xml,只需要用注解的方式就可以达到同样的效果


  1. springboot的环境搭建

环境要求:

  1. MAVEN 3.x+
  2. Spring FrameWork 4.x+
  3. JDK7.x +
  4. Spring Boot 1.5.x+

3.1 项目中引入依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
java复制代码 <!--继承springboot的父项目-->
   <parent>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-parent</artifactId>
       <version>1.5.7.RELEASE</version>
   </parent>

   <dependencies>
       <!--引入springboot的web支持-->
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-web</artifactId>
       </dependency>
   </dependencies>

3.2 引入配置文件

项目中src/main/resources/application.yml

3.3 建包并创建控制器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
java复制代码//在项目中创建指定的包结构

/*
com
   +| xxx
  +| controller */
              @Controller
                   @RequestMapping("/hello")
                   public class HelloController {
                       @RequestMapping("/hello")
                       @ResponseBody
                       public String hello(){
                           System.out.println("======hello world=======");
                           return "hello";
                      }
                  }

3.4 编写入口类

1
2
3
4
5
6
7
8
9
10
java复制代码//在项目中如下的包结构中创建入口类 Application
/*
com
+| xxx                 */
           @SpringBootApplication
           public class Application {
               public static void main(String[] args) {
                   SpringApplication.run(Application.class,args);
              }
          }

3.5 运行main启动项目

1
2
3
4
5
java复制代码o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8989 (http)
com.xxxx.Application : Started Application in 2.152 seconds (JVM running for 2.611)

//说明: 出现以上日志说明启动成功

3.6 访问项目

1
2
ruby复制代码//注意: springboot的项目默认没有项目名
//访问路径: http://localhost:8080/hello/hello


  1. 启动tomcat端口占用问题

在application.yml配置

1
2
3
yaml复制代码server:
port: 8989                 #用来指定内嵌服务器端口号
context-path: /springboot  #用来指定项目的访问路径


  1. springboot相关注解说明

1
2
3
4
5
6
7
8
9
10
11
kotlin复制代码说明: 

spring boot通常有一个名为 xxxApplication的类,入口类中有一个main方法, 在main方法中使用
SpringApplication.run(xxxApplication.class,args)启动springboot应用的项目。

@RestController: 就是@Controller+@ResponseBody组合,支持RESTful访问方 式,返回结果都是json字符串。

@SpringBootApplication 注解等价于下面三个注解:
@Configuration           项目启动时自动配置spring 和 springmvc 初始搭建
@EnableAutoConfiguration 自动与项目中集成的第三方技术进行集成
   @ComponentScan 扫描入口类所在子包以及子包后代包中注解


  1. springboot中配置文件的拆分

1
bash复制代码#说明: 在实际开发过程中生产环境和测试环境有可能是不一样的 因此将生产中的配置和测试中的配置拆分开,是非常必要的在springboot中也提供了配置文件拆分的方式. 这里以生产中项名名称不一致为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
yaml复制代码 生产中项目名为: cmfz
测试中项目名为: springboot
端口同时为:   8080

拆分如下:
#主配置文件:
application.yml #用来书写相同的的配置
server:
port: 8080 #生产和测试为同一个端口
                 
   #生产配置文件:
  application-pord.yml
  server:
  context-path: /cmfz
   #测试配置文件:
  application-dev.yml
  server:
  context-path: /springboot


  1. springboot中集成jsp展示

7.1 引入jsp的集成jar包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
xml复制代码<dependency>
   <groupId>jstl</groupId>
   <artifactId>jstl</artifactId>
   <version>1.2</version>
</dependency>

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-tomcat</artifactId>
</dependency>

<dependency>
   <groupId>org.apache.tomcat.embed</groupId>
   <artifactId>tomcat-embed-jasper</artifactId>
</dependency>

7.2 引入jsp运行插件

1
2
3
4
5
6
7
8
9
10
xml复制代码<build>
   <finalName>springboot_day1</finalName>
   <!--引入jsp运行插件-->
   <plugins>
       <plugin>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-maven-plugin</artifactId>
       </plugin>
   </plugins>
</build>

7.3 配置视图解析器

1
2
3
4
5
6
yaml复制代码#在配置文件中引入视图解析器
spring:
  mvc:
    view:
      prefix: /     # /代表访问项目中webapp中页面
      suffix: .jsp

7.4 启动访问jsp页面

1
bash复制代码http://localhost:8989/cmfz/index.jsp


  1. springboot集成mybatis

8.1 引入依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
xml复制代码<!--整合mybatis-->
<dependency>
   <groupId>org.mybatis.spring.boot</groupId>
   <artifactId>mybatis-spring-boot-starter</artifactId>
   <version>1.3.3</version>
</dependency>

<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.12</version>
</dependency>

<dependency>
   <groupId>mysql</groupId>
   <artifactId>mysql-connector-java</artifactId>
   <version>5.1.38</version>
</dependency>

1
makefile复制代码>说明:由于springboot整合mybatis版本中默认依赖mybatis 因此不需要额外引入mybati版本,否则会出现冲突

8.2 配置配置文件

1
2
3
4
5
6
7
8
9
10
11
yaml复制代码spring:
  mvc:
    view:
      prefix: /
      suffix: .jsp
  datasource:
    type: org.apache.commons.dbcp.BasicDataSource   #指定连接池类型
    driver-class-name: com.mysql.jdbc.Driver        #指定驱动
    url: jdbc:mysql://localhost:3306/cmfz           #指定url
    username: root                  #指定用户名
    password: xxxx                  #指定密码

8.3 加入mybatis配置

1
2
3
4
5
yaml复制代码#配置文件中加入如下配置:

mybatis:
mapper-locations: classpath:com/baizhi/mapper/*.xml  #指定mapper配置文件位置
type-aliases-package: com.xxxx.entity              #指定起别名来的类

1
2
3
4
5
6
7
8
less复制代码//入口类中加入如下配置:
@SpringBootApplication
@MapperScan("com.baizhi.dao")   //必须在入口类中加入这个配置
public class Application {
   public static void main(String[] args) {
       SpringApplication.run(Application.class,args);
  }
}

8.4 建表

8.5 开发实体类

8.6 开发DAO接口以及Mapper

8.7 开发Service以及实现

8.8 引入测试依赖

1
2
3
4
5
xml复制代码<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

8.9 编写测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
less复制代码@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
public class TestClazzService {

   @Autowired
   private ClazzService clazzService;

   @Test
   public void test(){
       List<Clazz> all = clazzService.findAll();
       for (Clazz clazz : all) {
           System.out.println(clazz);
      }

  }
}

本文转载自: 掘金

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

如何使用Go语言写出面向对象风格的代码

发表于 2021-11-14

原文链接:如何使用Go语言写出面向对象风格的代码

前言

哈喽,大家好,我是asong。在上一篇文章:小白也能看懂的context包详解:从入门到精通 分析context的源码时,我们看到了一种编程方法,在结构体里内嵌匿名接口,这种写法对于大多数初学Go语言的朋友看起来是懵逼的,其实在结构体里内嵌匿名接口、匿名结构体都是在面向对象编程中继承和重写的一种实现方式,之前写过java、python对面向对象编程中的继承和重写应该很熟悉,但是转Go语言后写出的代码都是面向过程式的代码,所以本文就一起来分析一下如何在Go语言中写出面向对象的代码。

面向对象程序设计是一种计算机编程架构,英文全称:Object Oriented Programming,简称OOP。OOP的一条基本原则是计算机程序由单个能够起到子程序作用的单元或对象组合而成,OOP达到了软件工程的三个主要目标:重用性、灵活性和扩展性。OOP=对象+类+继承+多态+消息,其中核心概念就是类和对象。

这一段话在网上介绍什么是面向对象编程时经常出现,大多数学习Go语言的朋友应该也都是从C++、python、java转过来的,所以对面向对象编程的理解应该很深了,所以本文就没必要介绍概念了,重点来看一下如何使用Go语言来实现面向对象编程的编程风格。

类

Go语言本身就不是一个面向对象的编程语言,所以Go语言中没有类的概念,但是他是支持类型的,因此我们可以使用struct类型来提供类似于java中的类的服务,可以定义属性、方法、还能定义构造器。来看个例子:

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
go复制代码type Hero struct {
Name string
Age uint64
}

func NewHero() *Hero {
return &Hero{
Name: "盖伦",
Age: 18,
}
}

func (h *Hero) GetName() string {
return h.Name
}

func (h *Hero) GetAge() uint64 {
return h.Age
}


func main() {
h := NewHero()
print(h.GetName())
print(h.GetAge())
}

这就一个简单的 “类”的使用,这个类名就是Hero,其中Name、Age就是我们定义的属性,GetName、GetAge这两个就是我们定义的类的方法,NewHero就是定义的构造器。因为Go语言的特性问题,构造器只能够依靠我们手动来实现。

这里方法的实现是依赖于结构体的值接收者、指针接收者的特性来实现的。

封装

封装是把一个对象的属性私有化,同时提供一些可以被外界访问的属性和方法,如果不想被外界访问,我们大可不必提供方法给外界访问。在Go语言中实现封装我们可以采用两种方式:

  • Go语言支持包级别的封装,小写字母开头的名称只能在该包内程序中可见,所以我们如果不想暴露一些方法,可以通过这种方式私有包中的内容,这个理解比较简单,就不举例子了。
  • Go语言可以通过 type 关键字创建新的类型,所以我们为了不暴露一些属性和方法,可以采用创建一个新类型的方式,自己手写构造器的方式实现封装,举个例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
go复制代码type IdCard string

func NewIdCard(card string) IdCard {
return IdCard(card)
}

func (i IdCard) GetPlaceOfBirth() string {
return string(i[:6])
}

func (i IdCard) GetBirthDay() string {
return string(i[6:14])
}

声明一个新类型IdCard,本质是一个string类型,NewIdCard用来构造对象,

GetPlaceOfBirth、GetBirthDay就是封装的方法。

继承

Go并没有原生级别的继承支持,不过我们可以使用组合的方式来实现继承,通过结构体内嵌类型的方式实现继承,典型的应用是内嵌匿名结构体类型和内嵌匿名接口类型,这两种方式还有点细微差别:

  • 内嵌匿名结构体类型:将父结构体嵌入到子结构体中,子结构体拥有父结构体的属性和方法,但是这种方式不能支持参数多态。
  • 内嵌匿名接口类型:将接口类型嵌入到结构体中,该结构体默认实现了该接口的所有方法,该结构体也可以对这些方法进行重写,这种方式可以支持参数多态,这里要注意一个点是如果嵌入类型没有实现所有接口方法,会引起编译时未被发现的运行错误。

内嵌匿名结构体类型实现继承的例子

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
go复制代码type Base struct {
Value string
}

func (b *Base) GetMsg() string {
return b.Value
}


type Person struct {
Base
Name string
Age uint64
}

func (p *Person) GetName() string {
return p.Name
}

func (p *Person) GetAge() uint64 {
return p.Age
}

func check(b *Base) {
b.GetMsg()
}

func main() {
m := Base{Value: "I Love You"}
p := &Person{
Base: m,
Name: "asong",
Age: 18,
}
fmt.Print(p.GetName(), " ", p.GetAge(), " and say ",p.GetMsg())
//check(p)
}

上面注释掉的方法就证明了不能进行参数多态。

内嵌匿名接口类型实现继承的例子

直接拿一个业务场景举例子,假设现在我们现在要给用户发一个通知,web、app端发送的通知内容都是一样的,但是点击后的动作是不一样的,所以我们可以进行抽象一个接口OrderChangeNotificationHandler来声明出三个公共方法:GenerateMessage、GeneratePhotos、generateUrl,所有类都会实现这三个方法,因为web、app端发送的内容是一样的,所以我们可以抽相出一个父类OrderChangeNotificationHandlerImpl来实现一个默认的方法,然后在写两个子类WebOrderChangeNotificationHandler、AppOrderChangeNotificationHandler去继承父类重写generateUrl方法即可,后面如果不同端的内容有做修改,直接重写父类方法就可以了,来看例子:

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
go复制代码type Photos struct {
width uint64
height uint64
value string
}

type OrderChangeNotificationHandler interface {
GenerateMessage() string
GeneratePhotos() Photos
generateUrl() string
}


type OrderChangeNotificationHandlerImpl struct {
url string
}

func NewOrderChangeNotificationHandlerImpl() OrderChangeNotificationHandler {
return OrderChangeNotificationHandlerImpl{
url: "https://base.test.com",
}
}

func (o OrderChangeNotificationHandlerImpl) GenerateMessage() string {
return "OrderChangeNotificationHandlerImpl GenerateMessage"
}

func (o OrderChangeNotificationHandlerImpl) GeneratePhotos() Photos {
return Photos{
width: 1,
height: 1,
value: "https://www.baidu.com",
}
}

func (w OrderChangeNotificationHandlerImpl) generateUrl() string {
return w.url
}

type WebOrderChangeNotificationHandler struct {
OrderChangeNotificationHandler
url string
}

func (w WebOrderChangeNotificationHandler) generateUrl() string {
return w.url
}

type AppOrderChangeNotificationHandler struct {
OrderChangeNotificationHandler
url string
}

func (a AppOrderChangeNotificationHandler) generateUrl() string {
return a.url
}

func check(handler OrderChangeNotificationHandler) {
fmt.Println(handler.GenerateMessage())
}

func main() {
base := NewOrderChangeNotificationHandlerImpl()
web := WebOrderChangeNotificationHandler{
OrderChangeNotificationHandler: base,
url: "http://web.test.com",
}
fmt.Println(web.GenerateMessage())
fmt.Println(web.generateUrl())

check(web)
}

因为所有组合都实现了OrderChangeNotificationHandler类型,所以可以处理任何特定类型以及是该特定类型的派生类的通配符。

多态

多态是面向对象编程的本质,多态是支代码可以根据类型的具体实现采取不同行为的能力,在Go语言中任何用户定义的类型都可以实现任何接口,所以通过不同实体类型对接口值方法的调用就是多态,举个例子:

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
go复制代码type SendEmail interface {
send()
}

func Send(s SendEmail) {
s.send()
}

type user struct {
name string
email string
}

func (u *user) send() {
fmt.Println(u.name + " email is " + u.email + "already send")
}

type admin struct {
name string
email string
}

func (a *admin) send() {
fmt.Println(a.name + " email is " + a.email + "already send")
}

func main() {
u := &user{
name: "asong",
email: "你猜",
}
a := &admin{
name: "asong1",
email: "就不告诉你",
}
Send(u)
Send(a)
}

总结

归根结底面向对象编程就是一种编程思想,只不过有些语言在语法特性方面更好的为这种思想提供了支持,写出面向对象的代码更容易,但是写代码的还是我们自己,并不是我们用了java就一定会写出更抽象的代码,在工作中我看到用java写出面向过程式的代码不胜其数,所以无论用什么语言,我们都应该思考如何写好一份代码,大量的抽象接口帮助我们精简代码,代码是优雅了,但也会面临着可读性的问题,什么事都是有两面性的,写出好代码的路还很长,还需要不断探索…………。

文中示例代码已经上传github:github.com/asong2020/G…

欢迎关注公众号:Golang梦工厂

本文转载自: 掘金

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

OpenFeign入门

发表于 2021-11-14

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

1、简介

Feign是一个REST客户端库,它通过接口驱动的方式来定义REST客户端。Spring Cloud Netflix体系中的Eureka服务注册中心客户端支持Ribbon客户端负载均衡器,而Feign本质上是Ribbon的包装,其内部是通过Ribbon来进行服务查找和负载均衡。

在Spring Cloud Netflix体系中,我们通常会通过以下两种方式进行服务通信:

  1. 支持负载均衡的RestTemplate
  2. Feign生成的客户端接口

两种方式都是通过Ribbon进行服务查找,然后更加负载均衡策略选择服务进行服务通信,具体使用那种方式看个人爱好(小捌觉得Feign客户端接口这种方式,在代码上稍显复杂,但是更好管理)。

关于Feign的一点小知识:

大家经常听到有人说OpenFeign,有人说Feign,给人一种好像是两个东西的错觉。其实是因为Feign本身也是Netflix的开源项目,后面独立出来单独做了开源项目,改名为OpenFeign。这种情况其实很常见,比如鸿蒙-HarmonyOS就有Open HarmonyOS。

2、正文

正式开始Feign的学习之前,大家需要对微服务有一定的认识,关于这些知识点可以查看本专栏的往期文章,入门微服务、Eureka相关知识,并且搭建一个Eureka服务和客户端之后在来学习Feign。

2.1 服务搭建

搭建一个Eureka Server用于服务注册发现,此外准备两个相同的服务提供者SERVER和一个服务消费者CONSUMER,整体的服务如下所示:

2.2 RestTemplate

由于Feign和RestTemplate一样如果需要进行查找服务,都是通过集成Ribbon来实现的,所以这里向看在上面这种微服务架构中,如何通过RestTemplate来消费服务。这里贴出的服务端代码,在后面的Feign中是相同的,后面将不会再贴出。

1、服务提供者Server-01暴露REST端点:

2、服务提供者Server-02暴露REST端点:


3、声明RestTemplate bean,并且添加注解 @LoadBalanced,这个注解会告诉Spring Boot这个RestTemplate需要具有通过Ribbon查找服务的能力,我这里因为演示所以在启动类中声明。


4、服务消费者Consumer通过RestTemplate消费服务代码:

这里服务提供者有两个,我们借助Ribbon和Eureka客户端可以实现服务的发现和负载均衡,注意服务地址不再硬编码,而是编写成服务提供者注册在Eureka上的服务名称server(不区分大小写)。

5、通过任意Http客户端请求Consumer提供的/consumer端点,不断刷新请求,可以看到RestTemplate 会依次轮训Server-01和Server-02

2.3 Feign Client

1、导入依赖

2、添加配置类启动Feign Client,可以直接在启动类上配置

3、定义Feign接口

这个接口定义完成,不需要实现类。在Spring boot运行时,当Feign发现了这个接口,Feign会自动创建一个实现类并将其暴露为Spring应用上下文中的bean

4、通过Feign客户端接口发送请求

注入Feign接口,可以直接调用接口中的方法(具体实现由Feign完成)即可发起请求。

5、通过任意Http客户端请求Consumer提供的/consumer端点,不断刷新请求,可以看到Feign Client会依次轮训Server-01和Server-02

2.4 总结

这篇文章入门了Feign,也顺带讲了下RestTemplate的简单使用。需要注意的是Fegin它并不负责解析服务名、负载均衡,这些都是其集成的Ribbon实现的。Feign可以替代RestTemplate,相比之下代码的可读性确实增强了,但是整体性能是有些许下降的。

本文转载自: 掘金

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

golang 微服务容错处理是如何做的?

发表于 2021-11-14

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

随着微服务的规模越来越大,各个微服务之间可能会存在错综复杂的调用关系

在我们实际工作中,确实慢慢的也出现了很多问题,整个系统的弊端的慢慢的展现出来

例如就会有这样的情况:

  • 服务 A 去请求服务B,服务 B 还需要去请求 服务 C,由于服务 C 的问题,导致整条链路都出现了问题,甚至整个系统都坏掉

工作中,我们一般为了提高服务的健壮性,会去设置失败后重试机制,用来避免一些因为网络抖动,暂时性的故障

可是,如果是一个长期性的故障,那么这个重试机制,只会加重我们服务的负担,一直在消耗连接和性能

这个时候,就需要服务熔断机制了

服务熔断机制

服务的熔断机制是什么呢?

其实熔断,是我们以前学习物理知识的时候听到过的词,例如家里的电路,在总开关的位置,都会有一个保险丝来保障我们电路的安全,若是出现了短路,或者是电流异常过大的情况下,保险丝就会因为过热而被熔断,进而断电,最终达到保护电表和电路的作用

在如今的微服务架构中,也需要有这一根保险丝

如上图,是一个很常见的微服务之间的调用关系

请求 :客户端 – 网关 – 服务A – 服务B

响应:服务B – 服务A – 网关 – 客户端

整条链路中,只要有一个点出现问题,客户端都无法得到期望的结果

在微服务架构中,服务之间的调用一般分为

  • 服务调用方
  • 服务提供方

为什么需要熔断?

当下游的服务因为过载或故障,无法提供服务,我们需要及时的让上游服务知悉,且暂时 熔断 调用方和提供方的调用链,这是为了避免服务雪崩现象的发生

服务雪崩

服务雪崩就是指调用链中的某个环节不可用了,此处特别指的是服务的提供方,导致上游服务不可用,并最终影响像雪崩一样扩散到整个系统,最终导致整个系统都不可用

简单故障场景

还是上面的一个熟悉场景,正常的 客户端 – 网关 – 服务A – 服务B 请求过程中,上面展示了三个阶段,这也是服务雪崩一般的 3 个阶段

  • 服务提供者不可用

系统运行之处,一切运转良好。每个服务正常请求和响应,当某一个刻,服务 B 由于 自身异常,或者网络故障导致自身不可用,无法及时的响应打过来的各种请求

  • 服务调用者不可用

在 服务B 作为服务提供者不可用的时候,客户端可能会因为错误提示,或者长时间的阻塞而不断的发送相同的请求到网关去,请求再次发送到网关,发送到 服务 A,最终又到 服务 B 知道超时也没有正常响应

重复多次,因为服务 A发起了过多的请求给到服务 B 而产生的等待线程,耗尽了线程池中的资源,那么 服务 A 自身也无法及时响应外部的请求,最终导致 服务 A 也不可用

  • 整个系统不可用

经过上述的流程,服务 A同样也阻塞了转发请求的网关,网关因为大量的等待请求响应也会产生大量的阻塞线程,同样的道理,网关最后没有足够的资源去处理其他请求,这样就导致整个系统无法对外提供服务

加上服务融到保障系统的可用性

如上图,服务 A 访问 服务 B 的过程中,中间加了一个保险丝,也就是一个断路器,

  • 当服务 A 访问 服务 B 的时候,服务 B 这时出现了轻微故障,导致超时返回
  • 服务 A 又 继续访问 服务 B 的时候,服务 B 已经不可用了,导致相应失败
  • 此时断路器检测到异常,则打开保险丝,设置异常返回
  • 服务 A 再次访问服务 B,保险丝自身就立即返回 错误消息给到 服务 A,这样避免服务 A 资源耗尽而不可用,进而保护了服务调用者

断路器

如上图,断路器有 3 中状态互相切换,我们可以这样来理解:

1 关闭状态 – 打开状态

  • 周期内函数执行失败超出阈值,就会从关闭状态到打开状态

2 打开状态 – 半开状态

  • 一定时候后,断路器会尝试执行请求函数,就会转到半开状态

3 半开 – 关闭

  • 尝试执行请求成功次数超过设定的阈值,就会转到关闭状态

4 半开状态 – 打开状态

  • 尝试执行请求函数成功次数没有超过设定的阈值,就会转到打开状态

今天就到这里,学习所得,若有偏差,还请斧正

欢迎点赞,关注,收藏

朋友们,你的支持和鼓励,是我坚持分享,提高质量的动力

好了,本次就到这里

技术是开放的,我们的心态,更应是开放的。拥抱变化,向阳而生,努力向前行。

我是小魔童哪吒,欢迎点赞关注收藏,下次见~

本文转载自: 掘金

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

MySQL操作符之(and、or、in、not)

发表于 2021-11-14

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

1、简介

在MySQL中使用where子句对查询数据进行过滤时,往往需要同时满足多个过滤条件,或者满足多个过滤条件中的某一个条件,此时我们就可以使用操作符将where子句联结起来。

几个操作符的作用:

操作符 作用
and 与,需要同时满足where子句中的条件
or 或,只需要匹配多个where子句中的一个条件
in 用于指定where子句查询的范围
not 非,一般与in、between and、exists一起使用,表示取反

2、正文

首先准备一张User表,DDL和表数据如下所示,可以直接复制使用。

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
sql复制代码SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '用户名',
`age` int(11) NOT NULL COMMENT '年龄',
`sex` smallint(6) NOT NULL COMMENT '性别',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 8 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES (1, '李子捌', 18, 1);
INSERT INTO `user` VALUES (2, '张三', 22, 1);
INSERT INTO `user` VALUES (3, '李四', 38, 1);
INSERT INTO `user` VALUES (4, '王五', 25, 1);
INSERT INTO `user` VALUES (5, '六麻子', 13, 0);
INSERT INTO `user` VALUES (6, '田七', 37, 1);
INSERT INTO `user` VALUES (7, '谢礼', 18, 0);

SET FOREIGN_KEY_CHECKS = 1;

数据的初始顺序如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
sql复制代码mysql> select * from user;
+----+--------+-----+-----+
| id | name | age | sex |
+----+--------+-----+-----+
| 1 | 李子捌 | 18 | 1 |
| 2 | 张三 | 22 | 1 |
| 3 | 李四 | 38 | 1 |
| 4 | 王五 | 25 | 1 |
| 5 | 六麻子 | 13 | 0 |
| 6 | 田七 | 37 | 1 |
| 7 | 谢礼 | 18 | 0 |
+----+--------+-----+-----+
7 rows in set (0.00 sec)

2.1 and操作符

当查询需要同时满足where子句中的条件,可以使用and操作符,and条件之间是一个与的关系。

需求:

查询年龄=18 并且 性别为男的用户(注意:sex=1代表男性)

语句:

1
sql复制代码mysql> select * from user where age = 18 and sex =1;

结果:

1
2
3
4
5
6
sql复制代码+----+--------+-----+-----+
| id | name | age | sex |
+----+--------+-----+-----+
| 1 | 李子捌 | 18 | 1 |
+----+--------+-----+-----+
1 row in set (0.00 sec)

此时可以看到只有同时满足age=18和sex=1的用户才被查询出来。以此类推,and可以同时存在多个,比如在上面的基础上需要查询 姓名=李子柒,只需要再跟一个and操作符即可。

1
2
sql复制代码mysql> select * from user where age = 18 and sex =1 and name = '李子柒';
Empty set (0.00 sec)

2.2 or操作符

与and不同,or只需要满足多个where条件中的一个即可,不需要同时满足,条件之间是一个或的关系。


需求:

查询年龄=18 或者 性别为男的用户(注意:sex=1代表男性)

语句:

1
sql复制代码mysql> select * from user where age = 18 or sex =1;

结果:

1
2
3
4
5
6
7
8
9
10
11
sql复制代码+----+--------+-----+-----+
| id | name | age | sex |
+----+--------+-----+-----+
| 1 | 李子捌 | 18 | 1 |
| 2 | 张三 | 22 | 1 |
| 3 | 李四 | 38 | 1 |
| 4 | 王五 | 25 | 1 |
| 6 | 田七 | 37 | 1 |
| 7 | 谢礼 | 18 | 0 |
+----+--------+-----+-----+
6 rows in set (0.00 sec)

此时可以看到,满足age=18或者sex=1的用户都被查出来了。同样的or操作符也可以同时作用于多个where子句。

2.3 in操作符

in操作符用于指定where子句的查询范围。它表示包含的意思,它可以用多个or操作符来实现。

需求:

查询name等于张三、李四、王五的用户信息。

语句:

使用or操作符

1
2
3
4
5
6
7
8
9
sql复制代码mysql> select * from user where name = '张三' or name = '李四' or name = '王五';
+----+------+-----+-----+
| id | name | age | sex |
+----+------+-----+-----+
| 2 | 张三 | 22 | 1 |
| 3 | 李四 | 38 | 1 |
| 4 | 王五 | 25 | 1 |
+----+------+-----+-----+
3 rows in set (0.00 sec)

使用in操作符

1
2
3
4
5
6
7
8
9
sql复制代码mysql> select * from user where name in ('张三', '李四', '王五');
+----+------+-----+-----+
| id | name | age | sex |
+----+------+-----+-----+
| 2 | 张三 | 22 | 1 |
| 3 | 李四 | 38 | 1 |
| 4 | 王五 | 25 | 1 |
+----+------+-----+-----+
3 rows in set (0.00 sec)

上面的需求,可以通过or操作符和in操作符来实现,但是in操作符很明显SQL语句根据简洁。

2.4 not操作符

当我们需要查询某个值不在什么范围之内、不存在的时候,可以使用not操作符,not操作符不单独使用,它经常和in操作符、like操作符、between and、exists等一起使用。

not in

需求:

查询姓名不等于张三、李四、王五的用户信息。

语句:

1
2
3
4
5
6
7
8
9
10
sql复制代码mysql> select * from user where name not in ('张三', '李四', '王五');
+----+--------+-----+-----+
| id | name | age | sex |
+----+--------+-----+-----+
| 1 | 李子捌 | 18 | 1 |
| 5 | 六麻子 | 13 | 0 |
| 6 | 田七 | 37 | 1 |
| 7 | 谢礼 | 18 | 0 |
+----+--------+-----+-----+
4 rows in set (0.00 sec)

not like

需求:

查询姓名不是以李子开头的用户

语句:

1
2
3
4
5
6
7
8
9
10
11
12
sql复制代码mysql> select * from user where name not like '李子%';
+----+--------+-----+-----+
| id | name | age | sex |
+----+--------+-----+-----+
| 2 | 张三 | 22 | 1 |
| 3 | 李四 | 38 | 1 |
| 4 | 王五 | 25 | 1 |
| 5 | 六麻子 | 13 | 0 |
| 6 | 田七 | 37 | 1 |
| 7 | 谢礼 | 18 | 0 |
+----+--------+-----+-----+
6 rows in set (0.00 sec)

not between and

需求:

查询年龄不属于20 - 30之间的用户

语句:

1
2
3
4
5
6
7
8
9
10
11
sql复制代码mysql> select * from user where age not between 20 and 30;
+----+--------+-----+-----+
| id | name | age | sex |
+----+--------+-----+-----+
| 1 | 李子捌 | 18 | 1 |
| 3 | 李四 | 38 | 1 |
| 5 | 六麻子 | 13 | 0 |
| 6 | 田七 | 37 | 1 |
| 7 | 谢礼 | 18 | 0 |
+----+--------+-----+-----+
5 rows in set (0.00 sec)

not exists

not exists表,它与exists用法一致,用于判断当前where子句的结果是否应该返回。not exists 和 exists作用于一个子查询,向上级返回true和false;

示例语法:

1
2
sql复制代码SELECT … FROM table WHERE EXISTS (subquery)
SELECT … FROM table WHERE NOT EXISTS (subquery)

为了演示效果,我们创建一个简单的订单表order,其建表语句和数据如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
sql复制代码SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for order
-- ----------------------------
DROP TABLE IF EXISTS `order`;
CREATE TABLE `order` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
`number` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '订单号',
`user_id` bigint(20) NULL DEFAULT NULL COMMENT '用户id',
`price` decimal(10, 2) NULL DEFAULT NULL COMMENT '金额',
`create_date` datetime(0) NULL DEFAULT NULL COMMENT '创建日期',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of order
-- ----------------------------
INSERT INTO `order` VALUES (1, 'DD-20211110-000001', 1, 250.00, '2021-11-10 22:37:19');

SET FOREIGN_KEY_CHECKS = 1;

注意:由于order是MySQL的关键字,所以建表时不建议直接取名为order,我这里取名order是为了讲述如何解决这个问题。

1
2
3
4
5
6
7
sql复制代码mysql> select * from `order`;
+----+--------------------+---------+--------+---------------------+
| id | number | user_id | price | create_date |
+----+--------------------+---------+--------+---------------------+
| 1 | DD-20211110-000001 | 1 | 250.00 | 2021-11-10 22:37:19 |
+----+--------------------+---------+--------+---------------------+
1 row in set (0.00 sec)

细心可以发现,order用 ` 修饰,这样MySQL就不会把它当成关键字解析了。如果不加MySQL会抛出异常。

回归主题,我们此时使用exists进行查询

需求:

查询已下单的用户信息

语句:

1
2
3
4
5
6
7
sql复制代码mysql> select * from user where exists(select id from `order` where user_id = user.id);
+----+--------+-----+-----+
| id | name | age | sex |
+----+--------+-----+-----+
| 1 | 李子捌 | 18 | 1 |
+----+--------+-----+-----+
1 row in set (0.00 sec)

此时如果我们想查询未下单的用户信息,只需要使用not exists即可

1
2
3
4
5
6
7
8
9
10
11
12
sql复制代码mysql> select * from user where not exists (select id from `order` where user_id = user.id);
+----+--------+-----+-----+
| id | name | age | sex |
+----+--------+-----+-----+
| 2 | 张三 | 22 | 1 |
| 3 | 李四 | 38 | 1 |
| 4 | 王五 | 25 | 1 |
| 5 | 六麻子 | 13 | 0 |
| 6 | 田七 | 37 | 1 |
| 7 | 谢礼 | 18 | 0 |
+----+--------+-----+-----+
6 rows in set (0.00 sec)

2.5 操作符顺序

上面说了好几个操作符,但是很多情况下需要多个操作符一起使用,这个时候我们就需要注意操作符的顺序问题了。

比如说如下需求:

查询用户表中,年龄大于20岁或者性别为男,并且姓名不等于张三的用户。

语句:

1
2
3
4
5
6
7
8
9
10
11
sql复制代码mysql> select * from user where age > 20 or sex = 1 and name != '张三';
+----+--------+-----+-----+
| id | name | age | sex |
+----+--------+-----+-----+
| 1 | 李子捌 | 18 | 1 |
| 2 | 张三 | 22 | 1 |
| 3 | 李四 | 38 | 1 |
| 4 | 王五 | 25 | 1 |
| 6 | 田七 | 37 | 1 |
+----+--------+-----+-----+
5 rows in set (0.00 sec)

此时发现查询的返回结果竟然包含张三,这是因为and的优先级比or高,在MySQL底层的SQL解析器,把上面的SQL解析成sex = 1 and name != ‘张三’ or age > 20 ;因为张三满足age > 20所以也被查询出来了。要想解决这个问题只需要使用括号将or语句括起来就好了。

1
2
3
4
5
6
7
8
9
10
sql复制代码mysql> select * from user where (age > 20 or sex = 1) and name != '张三';
+----+--------+-----+-----+
| id | name | age | sex |
+----+--------+-----+-----+
| 1 | 李子捌 | 18 | 1 |
| 3 | 李四 | 38 | 1 |
| 4 | 王五 | 25 | 1 |
| 6 | 田七 | 37 | 1 |
+----+--------+-----+-----+
4 rows in set (0.00 sec)

此时查询的返回数据中已经不包含张三了。

因此我们在写SQL的时候,可以养成习惯使用括号,通过括号对操作符分组,能够避免使用默认顺序带来的错误风险。

本文转载自: 掘金

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

Python开发基础总结(六)配置文件读取+异常+类型系统+

发表于 2021-11-14

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

一、配置文件读取的使用

1、 使用模块ConfigParser。实例如下:

1
2
3
4
5
6
vbscript复制代码conf = ConfigParser()
conf.read("snmp_agent.ini")

print(conf.get("main", "log_level"))
print(conf.getint("main", "ne_agent_port"))
print(conf.get("main", "ne_agent_qip"))

二、异常的使用

1、 尽量少用。它会使程序难以理解,而且还会发生不可预知的情况,比如异常的发生使程序的状态变为一个未知状态。

2、 可以寻找替代方案。

3、 程序非常重要,不可以停止,可以在主循环包装在异常处理中运行。

4、 打印出异常的信息,供后面的定位:log.error(traceback.format_exc())

5、 raise在引发异常的时候,可以传递引发一场的额外数据。形式如下:

raise Exception, 1

捕获方法:

except CallExit, e:

e就是那个额外数据1。(但是奇怪的是它的类型不是1)

6、 如何捕获一个异常,进行处理,然后在把它抛出:

1
2
3
4
5
ini复制代码   except :
        for flet in fletList:
            flet.throw()
        info = sys.exc_info()
        raise info[0], info[1], info[2]

7、 如何使用异常才是Pythonic的做法?这个要看一下。

三、类型系统

1、 类型也是对象。比如:inttype = int,然后,n = inttype(‘256’),这样可以把字符串转化为int值。

2、 另外,是否可以把字符串转化为关键字,或者对象?比如,一个变量,abc,是否可以通过’abc’来引用?

OS的使用

os中有很多可以直接利用的东西,比如,判断文件是否存在,删除文件等。这样可以不用再执行shell命令。

os.rremove(path):删除文件

os.system(‘ls’);执行shell命令

四、文件的使用

1、 打开使用函数open,模式和linux c类似。有一个不同的地方时,可以选择,直接操作磁盘还是操作内存。

2、 readline可以读取一个文件的一行。

3、 readlines:返回每一个列的列表。对应writelines。

4、 文件迭代器:

1
2
3
4
scss复制代码f = open(‘fliename’)
for line in f:
    process(line)
    f.close()

或者更简洁的:

1
2
scss复制代码 for line in open(filename):
        process(line)

5、 文件迭代器的使用:

如果文件很大,readlines可能会占用过多的内存。所以,Python提出一种类似于惰性求值的惰性迭代。

有两种方案:fileinput和文件迭代器:

1
2
3
scss复制代码import fileinput
for line in fileinput.input(filename)
        process(line)

文件迭代器:

1
2
3
scss复制代码f = open(filename)
for line in f:
        process(line)

6、 如何判断文件是否存在:

1
2
3
lua复制代码import os
os.path.isfile('/home/keepshell')
os.path.exists('/home/keepshell')

7、 如何判断目录是否存在:

1
2
3
lua复制代码import os
os.path.isdir('/home')
os.path.exists('/home')

\

​

本文转载自: 掘金

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

关于不同GC策略下的GC日志的解读 SerialGC Par

发表于 2021-11-14

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

昨天,我们介绍了GC日志,并且对并行GC进行GC日志的分析和解读,那我们就继续对不同GC策略都进行一个演示,来看看有什么不同,正好对专栏内提到的知识进行一个梳理和整合。

SerialGC

image.png

串行GC,历史最悠久的垃圾回收器,回收时会触发STW,我们看一下暂停时间是29ms,我们可以看到young区的大小 [DefNew: 279616K->34944K(314560K)压缩了大概248M,整个young区的大小大概是314M,我们整个的这个堆内存设置的是1G: 279616K>81630K(1013632K),那整个堆内存压缩之后是81M左右,那我们的Old区就是47M左右,和并行GC差不多,不过暂停时间比并行GC时间长,因为是单线程的,所以效率是比较低的。

ParallelGC

写过了,地址:juejin.cn/post/702992…

ConcurrentMarkSweepGC

我在专栏里提到过CMS垃圾回收器,它是和我们的这个ParNew一起使用的,因为后面G1GC就登场了,所以官方的意思是把它们两个全部取代。

我们可以看到young区[ParNew: 279616K->34944K(314560K), 0.0132556 secs],跟前面并行GC差不多,STW用了13毫秒。
image.png

Old区是用了CMS,然后是第一个阶段初始化标记:只标记我们的根对象和根对象可达的对象,所以速度是比较快的,就用了0.1毫秒,之后是并发标记、并发预清理阶段,中间还发生了几次youngGC,因为CMS主要是回收老年代的,所以中间会发生几次youngGC。
image.png

接下来,是进行并发的最终标记,然后并发清理,之后进行并发重置,等待下一次回收。
image.png

G1GC

可以看出G1GC的日志是非常之复杂,我一开始都看懵了,我们隐藏掉一些细节吧,
image.png

可以看出,我们的这个G1GC跟我们的这个CMSGC其实差不多,G1GC只是把我们这个java堆分成了很多的Region,我们可以看出当第一次这个分配大对象失败的时候,触发了我们的G1GC,关于G1GC,专栏里有写道:juejin.cn/post/699943… 不多提了。

image.png

总结

如果使用SerialGC,那垃圾收集就使用Serial+SerialOld的组合策略进行回收,由于是单线程的垃圾回收器,所以回收时占用的额外内存资源是所有垃圾回收器中占用最小的,而且在单核场景下,没有线程交互的开销,那对于桌面应用来说,无疑是一个比较好的选择,所以当虚拟机使用客户端模式下,默认的GC策略就是Serial。

如果使用ParallelGC,那垃圾收集器使用Parallel Scavenge+SerialOld的组合策略进行回收,如果说串行GC是单人干活,那并行GC就是多人干活,效率自然要高一些,并行GC的关注点是高吞吐量,但是它也有GC暂停的,可能时间会特别长,因此,也不适用于一些追求系统延迟的应用。

如果使用CmsGC,那垃圾收集器使用Parallel Scavenge+SerialOld的组合策略进行回收,
如果系统的关注点是减少回收的停顿时间,提高给用户的响应速度,那CMS是一个不错的选择,但CMS的缺点是无法处理浮动垃圾,而且对CPU的算力要求比较高。

如果使用G1GC,G1的设计目标是把垃圾收集的STW时间变成可控的,面向的是服务端的应用,适合大内存,低延迟的场景,但G1需要的CPU的算力远比CMS要高,而且G1的实现更为复杂,也会占用比CMS更多的内存空间。

注意

堆内存的设置其实是反映实际容纳对象的大小的,堆内存设置的越大,容纳的对象也越多,发生GC的次数也会少,但是会造成GC的暂停时间特别长,所以这个是我们需要权衡的一个点,反之亦然。

配置参数时,一定要把-Xmx -Xms设置成一样的,不然,会发生内存的抖动,不光发生了很多次的Full GC,还进行了扩容,得不偿失。

防止我们的GC退化,如果GC退化成Full GC,那对性能的影响是非常大的。

本文转载自: 掘金

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

手把手教 Docker Compose 快速启动pgAd

发表于 2021-11-14

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

众所周知,PostgreSQL的强大管理工具,包括两个,一个是pgAdmin,另外一个是Navicat,前者是开源免费的,后者是付费才能使用。安装pgAdmin的方式很多,可以通过各个系统的安装包进行安装使用,也可以通过容器安装后使用,本篇文章基于Docker Compose 搭建环境快速启动pgAdmin。

一、pgAdmin

pgAdmin是PostgreSQL最受欢迎的,拥有丰富的开源管理和开发平台,是世界上最先进的开源数据库。

PGAdmin可以用于Linux,UNIX,MacOS和Windows,用于管理PostgreSQL和EDB Advanced Server 9.5及更高版本。

PGAdmin 4是一种完整的PGAdmin,使用Python和JavaScript / jQuery构建,支持以下的方式安装使用。

image.png

pgAgent是PostgreSQL的作业计划程序,其可以使用pgAdmin来管理。

image.png

二、创建Docker Compose 文件并运行

首先创建docker-compose.yml文件,并填充以下内容:

1
2
3
4
5
6
7
8
9
10
11
yaml复制代码version: '3.8'
services:
pgadmin:
container_name: pgadmin4_container
image: dpage/pgadmin4
restart: always
environment:
PGADMIN_DEFAULT_EMAIL: admin@admin.com
PGADMIN_DEFAULT_PASSWORD: root
ports:
- "5050:80"

这里,我们的服务名称为pgadmin,为了方便起见,我们使用了这样的服务的标记格式container_name 将默认容器名更改为 pgadmin4_container。

然后,在docker-compose.yml 文件所在的同一个目录中运行以下命令。

1
复制代码docker compose up

通过docker compose up命令我们将启动并运行整个应用程序。接下来让我们将 pgadmin4连接到我们的 PostgreSQL 数据库服务器。

三、配置连接数据库服务

首先,通过浏览器访问该 http://{yourhost}:5050/ 。 使用 admin@admin.com和root作为登录账号密码。

image.png

单击 Servers > Create > Server 创建一个新服务器。

填写名称、主机名/地址、用户名和密码的数据。

image.png

image.png
配置完成,连接成功,即可开箱享用。

本文转载自: 掘金

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

SSIS学习使用七:中级SSIS工作流管理 翻译参考 中级S

发表于 2021-11-14

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

翻译参考

本文主要参考翻译自 the Stairway to Integration Services 系列文章的 Intermediate SSIS Workflow Management – Level 7 of the Stairway to Integration Services,目的在于对 SSIS 有一个全面清晰的认识,所有内容在原文的基础上进行实操,由于版本差异、个人疑问等多种原因,未采用完全翻译的原则,同时也会对文中内容进行适当修改,希望最终可以更有利于学习和了解 SSIS,

感谢支持!

中级SSIS工作流管理

在上一部分,我们构建一个新的 SSIS包,查看了 SSIS 中的脚本任务和优先约束,并检查了 MaxConcurrentExecutables 包属性。

本篇中,我们将检查、演示和测试优先约束的 “On Success”、“On Completion” 和 “On Failure” 功能。

约束评估(Constraint Evaluation)

在上一部分中有提到”优先约束评估”。它决定了”终止任务”的开始执行。而评估的类型分为以下几类。

On Success

优先约束评估的默认类型为”On Success”。

为了演示,在 Visual Studio 中运行 Precedence包。如下,首先,一个来自 “Script Task 1” 的消息框出现:

当点击消息框的确定按钮,”Script Task 1” 成功(右上角的绿色对勾表示成功),”Script Task 1” 和 “Script Task 2” 之间的评估为True,然后 “Script Task 2” 执行并显示如下消息框

点击 “Script Task 2” 消息框的“确定”按钮,”Precedence.dtsx”包 完成并成功。

此处的优先级约束配置为 “On Success” 评估。

停止包的运行。

On Completion (Success)

右击优先约束,查看右键菜单中可用的 “配置选项”(configuration options)。可以发现”编辑…”后面有 成功(Success)、失败(Failure) 和 完成(Completion)。

点击”完成”,优先约束的颜色会从绿色转换(深)蓝色(不同的版本颜色似乎有差异,原本成功的颜色是纯正的绿色,现在已经偏蓝绿色。原本完成是蓝色,现在是特别深的深蓝色,趋近于黑色了)。

现在 “Script Task 1” 和 “Script Task 2” 之间的优先约束配置为评估” On Completion”。“Script Task 1”无论成功还是失败,只要完成执行优先约束就会评估,且“Script Task 2”将执行。

调试运行 SSIS包,一旦 “Script Task 1” 执行成功,“Script Task 2” 将执行。

停止包的运行。

你可能会有疑问:“此处仅仅测试了’On Completion’配置的成功时的优先约束;我们如何知道在失败时起作用?”,答案是:“这种情况下,我们不知道’On Completion’配置在失败时的条件”。

下面测试失败时(On Failure)执行。

On Completion (Failure)

首先,我们生成一个失败的条件。

打开 “Script Task 1” 编辑器,点击”编辑脚本”。在Main()函数中加入对消息框返回的判断,根据判断结果返回 “脚本任务”(‘Script Task’) 是成功(点击yes按钮时)还是失败(不点击yes按钮时)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
cs复制代码public void Main()
{
// TODO: Add your code here
var sTaskName = Dts.Variables["TaskName"].Value.ToString();
var boxRes = MessageBox.Show(sTaskName + " Succeed?", "确认", MessageBoxButtons.YesNo);
if (boxRes == DialogResult.Yes)
{
Dts.TaskResult = (int)ScriptResults.Success;
}
else
{
Dts.TaskResult = (int)ScriptResults.Failure;
}
}

关闭脚本代码编辑器,并点”确定”关闭脚本任务编辑器。

执行SSIS包,将会出现如下消息框,点击 “是”(Yes) 按钮,”On Completion”优先约束 将会评估且 “Script Task 2” 将执行。这就是我们之前演示的用例。

如果点击 “否”(No)按钮,”Script Task 2”也将执行。

注:此时在修改完 “Script Task 1” 的脚本后,你会发现 “Script Task 2” 脚本的执行,出现的消息也跟着改变了。点进去编辑脚本查看,代码是没问题的。

解决办法:在 “Script Task 2” 的编辑脚本中随便出现个变动(如加个空格,加个回车换行等),并保存。再次执行将不会有此问题!

这个问题出现的原因是:我们复制并粘贴 “Script Task 1” 来创建 “Script Task 2”。有时,属于一个被复制的脚本任务的元数据,将导致来自一个脚本任务的代码在复制和粘贴的第二个脚本的上下文中执行。这种情况很少见,但是确实也发生了。

添加更改将使 SSIS 重新评估(“re-evaluate”)嵌入到 “Script Task 2” 中的脚本代码。从而按设计的代码执行。

我们知道了 “On Completion” 在上一个任务无论成功还是失败时都会执行。下面重新测试下”On Success”。

重新测试 On Success

右键“优先约束”,并设置为”成功”。

调试运行 SSIS,在 “Script Task 1” 消息框中点击 “否” 按钮,导致脚本任务失败。然后可以看到 “Script Task 2” 将不会执行。

如果发生异常或错误,我们使用 “On Success”优先约束 停止执行。

On Failure

停止当前的执行,右击 “优先约束” 并选择 “失败”(On Failure)。优先约束将会更改颜色为红色,标识它配置为 上一个任务在失败时评估并执行。

执行 SSIS 包,当 “Script Task 1” 出现提示框时,点击 “否” 按钮,”Script Task 2” 将会按如下执行。

可以重新运行,然后在 “Script Task 1” 的提示框中点击 “是”,”Script Task 1” 成功,但是 优先约束 不会评估,且 “Script Task 2” 不会执行。

停止当前的调试运行。

总结

本篇中,我们使用 SSIS 脚本任务完成测试多种用例的状态。使用测试状态演示 SSIS优先约束 如何表示上一个任务的成功、完成和失败条件。

本文转载自: 掘金

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

【Go语言入门150题】L1-063 吃鱼还是吃肉 (10

发表于 2021-11-14

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

【题解】【PTA团体程序设计天梯赛】

L1-063 吃鱼还是吃肉 (10 分) Go语言|Golang

国家给出了 8 岁男宝宝的标准身高为 130 厘米、标准体重为 27 公斤;8 岁女宝宝的标准身高为 129 厘米、标准体重为 25 公斤。

现在你要根据小宝宝的身高体重,给出补充营养的建议。

输入格式:

输入在第一行给出一个不超过 10 的正整数 N,随后 N 行,每行给出一位宝宝的身体数据:

1
复制代码性别 身高 体重

其中性别是 1 表示男生,0 表示女生。身高和体重都是不超过 200 的正整数。

输出格式:

对于每一位宝宝,在一行中给出你的建议:

  • 如果太矮了,输出:duo chi yu!(多吃鱼);
  • 如果太瘦了,输出:duo chi rou!(多吃肉);
  • 如果正标准,输出:wan mei!(完美);
  • 如果太高了,输出:ni li hai!(你厉害);
  • 如果太胖了,输出:shao chi rou!(少吃肉)。

先评价身高,再评价体重。两句话之间要有 1 个空格。

输入样例1:

1
2
3
4
5
in复制代码4
0 130 23
1 129 27
1 130 30
0 128 27

结尾无空行

输出样例1:

1
2
3
4
out复制代码ni li hai! duo chi rou!
duo chi yu! wan mei!
wan mei! shao chi rou!
duo chi yu! shao chi rou!

结尾无空行

思路:

基础的判断,先分开男女,然后再分身高和体重,再进行输出。

代码如下:

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
go复制代码package main

import "fmt"

func main() {
var N int
_,_=fmt.Scan(&N)
for i:=0;i<N;i++{
var sex,tall,weight int
_,_ = fmt.Scan(&sex,&tall,&weight)
if sex==1 {
if tall>130 {
fmt.Printf("ni li hai!")
} else if tall==130 {
fmt.Printf("wan mei!")
} else{
fmt.Printf("duo chi yu!")
}
if weight>27 {
fmt.Printf("shao chi rou!")
}else if weight==27 {
fmt.Printf("wan mei!")
} else{
fmt.Printf("duo chi rou!")
}
} else {
if tall > 129 {
fmt.Printf("ni li hai!")
} else if tall == 129 {
fmt.Printf("wan mei!")
} else {
fmt.Printf("duo chi yu!")
}
if weight > 25 {
fmt.Printf("shao chi rou!")
} else if weight == 25 {
fmt.Printf("wan mei!")
} else {
fmt.Printf("duo chi rou!")
}
}
if i != N-1 {
fmt.Println()
}
}
}

本文转载自: 掘金

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

1…349350351…956

开发者博客

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