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

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


  • 首页

  • 归档

  • 搜索

RocketMQ单机版安装 环境准备 RocketMQ安装

发表于 2021-11-25

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

在MQ中现在最常用的MQ分为三种:RabbitMQ,RocketMQ,Kafka,这这篇中主要介绍下RocketMQ单机版本的安装,帮助小白快速入门,能够快速的初始RocketMQ。
为了快速的入门学习RocketMQ,可以在虚拟机上面安装一个单机版的RocketMQ用于学习及入门使用,在接下来的步骤记录下我自己安装的操作,方便以后自己查看。单机版MQ在生产环境是禁止使用,RocketMQ支持集群安装,线上都采用集群安装模式。
在安装之前先对MQ的几个组件简单的描述:

  • NameServer:类似ZK,提供注册服务中心,为Broker提供轻量级的路由注册服务
  • Broker: 核心组件,实际负责消息的处理,存储,转发等服务
  • Produer:业务系统维护,负责MQ消息的发送(生产者)
  • Consumer:业务系统维护,负责MQ消息的消费(消费者)

在安装的时候,只需要安装NameServer及Broker,先启动NameServer后启动Broker。

环境准备

  • Centos 7 虚拟机一台
  • JDK 1.8 环境安装
  • RocketMQ安装包release包

下载地址:rocketmq.apache.org/docs/quick-…

  • RocketMQ界面控制台

由于官方未提供控制台,开源社区有一套控制台界面
gitcode.net/mirrors/apa…

RocketMQ安装

  • 将rocketmq-all-4.9.2-bin-release.zip解压并移动到:/usr/local/rocketmq 目录下,然后进入/usr/local/rocketmq 目录中启动

image.png

启动NameServer

启动NameServer比较简单,直接启动ROCKETMQHOME/bin目录下的mqnamesrv,就可以启动NameServer服务,但是我们是在虚拟机上面启动,RocketMQ启动NameServer的时候配置文件默认内存时4G,就需要将JVM的内存调小,不然无法启动。修改ROCKETMQ_HOME/bin目录下的mqnamesrv,就可以启动NameServer服务,但是我们是在虚拟机上面启动,RocketMQ启动NameServer的时候配置文件默认内存时4G,就需要将JVM的内存调小,不然无法启动。
修改 ROCKETMQH​OME/bin目录下的mqnamesrv,就可以启动NameServer服务,但是我们是在虚拟机上面启动,RocketMQ启动NameServer的时候配置文件默认内存时4G,就需要将JVM的内存调小,不然无法启动。修改ROCKETMQ_HOME/bin/runserver.sh 文件中JAVA_OPT参数

JAVA_OPT=”${JAVA_OPT} -server -Xms512m -Xmx512m -Xmn256m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=320m”

image.png

启动命令:

1
2
sql复制代码cd /usr/local/rocketmq
nohup bin/mqnamesrv &

启动日志会打印到当前文件夹下的nohup.out 中

启动Broker

Broker的启动步骤跟NameServer一样,需要启动ROCKETMQHOME/bin目录下的mqbroker,在虚拟机安装的时候,也需要将Jvm的内存改小,Broker默认的内存大小是8G。修改ROCKETMQ_HOME/bin目录下的mqbroker,在虚拟机安装的时候,也需要将Jvm的内存改小,Broker默认的内存大小是8G。
修改 ROCKETMQH​OME/bin目录下的mqbroker,在虚拟机安装的时候,也需要将Jvm的内存改小,Broker默认的内存大小是8G。修改ROCKETMQ_HOME/bin/runbroker.sh 文件中JAVA_OPT参数

JAVA_OPT=”${JAVA_OPT} -server -Xms512m -Xmx512m -Xmx512m -Xmn256m”

image.png
启动命令:

在启动的时候,需要指定启动配置文件,不然配置的参数不生效:broker.conf

1
2
sql复制代码cd /usr/local/rocketmq
nohup bin/mqbroker -c conf/broker.conf &
  • 安装控制台

由于控制台下载的是源码,则需要采用maven 命令打包成jar

mvn clean package -Dmaven.test.skip=true

将打包好的rocketmq-console-ng-1.0.0.jar上传到服务器,/usr/local/rocketmq下,然后启动:

1
2
sql复制代码cd /usr/local/rocketmq
nohup java -jar rocketmq-console-ng-1.0.1.jar &

启动成功后,访问虚拟机的IP:8080 就可访问
image.png如果下载的源码,没有在配置文件中(rocketmq.config.namesrvAddr)指定NameServer的地址,则需要在虚拟机的环境中配置NameServer的地址

#if this value is empty,use env value rocketmq.config.namesrvAddr NAMESRV_ADDR | now, you can set it in ops page.default localhost:9876
rocketmq.config.namesrvAddr=

在这里,就在虚拟机的环境中配置NAMESRV_ADDR 地址,在/etc/profile文件后面追加下面的参数,然后使配置文件生效

1
2
sql复制代码#rocketmq 
export NAMESRV_ADDR=localhost:9876

source /etc/profile

重启rocketmq-console-ng 即可

服务验证

查看端口使用启动

1
sql复制代码netstat -ntlp |grep java

image.png

可以看到启动了一堆端口

  • 9876:NameServer启动的端口,用于注册地址
  • 8080:控制台启动端口,用于界面访问
  • 10909/10911/10912:Broker内部通信端口

使用生产者工具发送消息

由于RocketMQ的配置不允许使用的时候创建Topic,则需要在$ROCKETMQ_HOME/conf/broker.conf 尾部增加配置,允许自动创建Topic

autoCreateTopicEnable=true

image.png

使用bin/tools.sh 发送消息,默认发送1000条消息

1
2
sql复制代码cd /usr/local/rocketmq/
bin/tools.sh org.apache.rocketmq.example.quickstart.Producer

image.png

使用消费者工具接收消息

使用bin/tools.sh 接收消息

1
2
sql复制代码cd /usr/local/rocketmq/
bin/tools.sh org.apache.rocketmq.example.quickstart.Consumer

image.png

到这里单机版的RocketMQ安装完成

关闭RocketMQ服务

关闭NameServer

1
2
sql复制代码cd /usr/local/rocketmq/
sh bin/mqshutdown namesrv

关闭Broker

1
2
sql复制代码cd /usr/local/rocketmq/
sh bin/mqshutdown broker

关闭控制台

找到8080端口的pid,使用kill -9 杀死

1
2
sql复制代码 netstat -ntlp |grep 8080
kill -9 1836

image.png

本文转载自: 掘金

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

雇工模式(Employee Pattern)

发表于 2021-11-25

本文节选自《设计模式就该这样学》

1 雇工模式的定义

雇工模式(Employee Pattern)也叫作仆人模式(Servant Pattern),属于行为型设计模式,它为一组类提供通用的功能,而不需要类实现这些功能,也是命令模式的一种扩展。

2 雇工模式的应用场景

在日常开发过程中,我们可能已经接触过雇工模式,只是没有把它抽取出来,也没有汇编成册。或许大家已经看出这与命令模式非常相似,其实雇工模式是命令模式的一种简化,但更符合实际需要,更容易进入开发场景中。

3 雇工模式的UML类图

雇工模式的UML类图如下图所示。

image.png

由上图可以看到,雇工模式主要包含3个角色。

(1)抽象服务提供者(Serviced):用于定义服务内容的接口。

(2)具体服务提供者(ConcreteServiced):实现所有的服务内容。

(3)雇工(Servant):即执行者,用于执行服务。

4 雇工模式的通用写法

以下是雇工模式的通用写法。

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
java复制代码
public class Client {

public static void main(String[] args) {
Servant servant = new Servant();
servant.service(new ConcreteServiced1());
servant.service(new ConcreteServiced2());
}
/**
* 通用功能
*/
interface IServiced {
//具有的特质或功能
public void serviced();
}

/**
* 具体功能
*/
static class ConcreteServiced1 implements IServiced {
public void serviced(){
System.out.println("Serviced 1 doing");
}
}

static class ConcreteServiced2 implements IServiced{
public void serviced(){
System.out.println("Serviced 2 doing");
}
}

/**
* 雇工类
*/
static class Servant {
//服务内容
public void service(IServiced serviceFuture){
serviceFuture.serviced();
}
}

}

5 雇工模式的优点

扩展性良好,可以很容易地增加雇工来执行新的任务。

6 雇工模式的缺点

增加了程序的复杂度。

【推荐】Tom弹架构:30个设计模式真实案例(附源码),挑战年薪60W不是梦

本文为“Tom弹架构”原创,转载请注明出处。技术在于分享,我分享我快乐!如果本文对您有帮助,欢迎关注和点赞;如果您有任何建议也可留言评论或私信,您的支持是我坚持创作的动力。关注『 Tom弹架构 』可获取更多技术干货!

本文转载自: 掘金

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

RabbitMQ从入门到放弃(三)

发表于 2021-11-25

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

Hello World

前提条件:本文建立在已安装并运行RabbitMQ的场景上。

官方的教程案例中有几个模式,其中Hello World为其中一个,也是最简单的一个。

image.png

上图中的P为生产者,C为消费者,中间红色部分为队列,用来保留生产者发送的消息。接下来我们将用Java编写两个程序:

  • 发送消息的生产者
  • 接收消息的消费者

SpringBoot整合

官网提供的案例采用的是手动创建链接的方式

1
2
3
4
5
6
java复制代码ConnectionFactory factory = new ConnectionFactory();
factory.setHost( "本地主机" );
尝试(连接连接= factory.newConnection();
通道通道 = connection.createChannel()) {

}

基于目前大多数项目使用了SpringBoot框架,因此本文将以SpringBoot整合的方式进行实现。

pom.xml

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

生产者

生产者接口,将消息发送至指定的RabbitMQ队列中。

在生产者类中,我们引用了RabbitTemplate对象,RabbitTemplate是RabbitMQ在与SpringAMQP整合的时,Spring提供的即时消息模板。

同时定义了队列的名称hello_world。

还有一个发送消息的接口,入参为需要发送的消息。

image.png

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
typescript复制代码/**
* 生产者: 发送消息的程序
*
* @author unidentifiable
* @date 2021/11/25
*/
@RestController
@RequestMapping("/producer")
public class Producer {

@Autowired
private RabbitTemplate rabbitTemplate;

/**
* 队列名称
*/
private static final String QUEUE_NAME = "hello_world";

/**
* 发现消息
*
* @param msg 待发送的消息
* @return java.lang.String
*/
@GetMapping("/sendMsg")
public String sendMsg(String msg){
// 将消息发送至指定的队列中
rabbitTemplate.send(QUEUE_NAME, new Message(msg.getBytes(StandardCharsets.UTF_8)));
return "消息【" + msg + "】已发出";
}
}

调用生产者接口,得到响应结果
image.png

通过MQ队列可以看到在指定的hello_world里面有一条信息待消费

image.png

消费者

消费者接口,监听来自RabbitMQ指定队列的消息。

image.png

在消费者类中,我们定义一个监听的队列QUEUE_NAME, 同时在方法入口上方加上注解@RabbitListener(queues = QUEUE_NAME)用于监听RabbitMQ中指定队列的消息,当队列中有消息的时候,该接口会触发监听事件,将其进行消费。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
java复制代码/**
* 消费者:接收消息的程序
*
* @author unidentifiable
* @date 2021/11/25
*/
@Component
public class Consumer {

private static final String QUEUE_NAME = "hello_world";

/**
* 监听队列
*/
@RabbitListener(queues = QUEUE_NAME)
public void receive(String msg) {
System.out.println("接收到的消息为:" + msg);
}
}

监听到MQ中有消息,消费并在控制台打印
image.png

查看队列,Ready已变为0
image.png

最后

文章有写的不好的地方,请大佬们不吝赐教,错误是最能让人成长的,愿我与大佬间的距离逐渐缩短!

如果觉得文章对你有帮助,请 点赞、收藏、关注、评论 一键四连支持,你的支持就是我创作最大的动力!!!

本文转载自: 掘金

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

空对象模式(Null Object Pattern)

发表于 2021-11-25

本文节选自《设计模式就该这样学》

1 空对象模式的定义

空对象模式(Null Object Pattern)不属于GoF设计模式,但是它作为一种经常出现的模式足以被视为设计模式了。其具体定义为设计一个空对象取代NULL对象实例的检查。NULL对象不是检查控制,而是反映一个不做任何动作的关系。这样的NULL对象也可以在数据不可用的时候提供默认的行为,属于行为型设计模式。

原文:Provide an object as a surrogate for the lack of an object of a given type. The Null object provides intelligent do nothing behavior, hiding the details from its collaborators.

2 空对象模式的应用场景

空对象模式适用于以下应用场景。

(1)对象实例需要一个协作实例。空对象模式不会引入协作实例,它只是使用现有的协作实例。

(2)部分协作实例不需要做任何处理。

(3)从客户端中将对象实例不存在的代码逻辑抽象出来。

3 空对象模式的UML类图

空对象模式的UML类图如下图所示。

file

由上图可以看到,空对象模式主要包含3个角色。

(1)抽象对象(AbstractObject):定义所有子类公有的行为和属性。

(2)真实对象(RealObject):继承AbstractObject类,并实现所有行为。

(3)空对象(NullObject):继承AbstractObject类,对父类方法和属性不做实现和赋值。

4 空对象模式的通用写法

以下是空对象模式的通用写法。

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
java复制代码
public class Client {

public static void main(String[] args) {
ObjectFactory factory = new ObjectFactory();
System.out.println(factory.getObject("Joe").isNill());
System.out.println(factory.getObject("Tom").isNill());
}


//抽象对象
static abstract class AbstractObject{
abstract void request();
abstract boolean isNill();
}

//空对象
static class NullObject extends AbstractObject{

public void request() {
System.out.println("Not Available Request");
}

boolean isNill() {
return true;
}
}

//真实对象
static class RealObject extends AbstractObject{
private String name;
public RealObject(String name) {
this.name = name;
}

public void request() {
System.out.println("Do samething...");
}

boolean isNill() {
return false;
}
}

//对象工厂
static class ObjectFactory{
private static final String[] names = {"Tom","Mic","James"};

public AbstractObject getObject(String name){
for (String n : names) {
if(n.equalsIgnoreCase(name)){
return new RealObject(name);
}
}
return new NullObject();
}
}
}

5 空对象模式的优点

(1)它可以加强系统的稳固性,能有效地减少空指针报错对整个系统的影响,使系统更加稳定。

(2)它能够实现对空对象情况的定制化的控制,掌握处理空对象的主动权。

(3)它并不依靠Client来保证整个系统的稳定运行。

(4)它通过定义isNull()对使用条件语句==null的替换,显得更加优雅,更加易懂。

6 空对象模式的缺点

每一个要返回的真实的实体都要建立一个对应的空对象模型,那样会增加类的数量。

【推荐】Tom弹架构:30个设计模式真实案例(附源码),挑战年薪60W不是梦

本文为“Tom弹架构”原创,转载请注明出处。技术在于分享,我分享我快乐!如果本文对您有帮助,欢迎关注和点赞;如果您有任何建议也可留言评论或私信,您的支持是我坚持创作的动力。关注『 Tom弹架构 』可获取更多技术干货!

本文转载自: 掘金

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

规格模式(Specification Pattern)

发表于 2021-11-25

本文节选自《设计模式就该这样学》

1 规格模式的定义

规格模式(Specification Pattern)可以认为是组合模式的一种扩展。很多时候程序中的某些条件决定了业务逻辑,这些条件就可以抽离出来以某种关系(与、或、非)进行组合,从而灵活地对业务逻辑进行定制。另外,在查询、过滤等应用场合中,通过预定义多个条件,然后使用这些条件的组合来处理查询或过滤,而不是使用逻辑判断语句来处理,可以简化整个实现逻辑。
这里的每个条件都是一个规格,多个规格(条件)通过串联的方式以某种逻辑关系形成一个组合式的规格。规格模式属于结构型设计模式。

2 规格模式的应用场景

规格模式主要适用于以下应用场景。

(1)验证对象,检验对象本身是否满足某些业务要求或者是否已经为实现某个业务目标做好了准备。

(2)从集合中选择符合特定业务规则的对象或对象子集。

(3)指定在创建新对象的时候必须要满足某种业务要求。

3 规格模式的UML类图

规格模式的UML类图如下图所示。

file

由上图可以看到,规格模式主要包含6个角色。

(1)抽象规格书(Specification):对规格书的抽象定义。

(2)组合规格书(CompositeSpecification):一般设计为抽象类,对规格书进行与或非操作,实现and()、or()、not()方法,在方法中关联子类,因为子类为固定类,所以父类可以进行关联。

(3)与规格书(AndSpecification):对规格书进行与操作,实现isSatisfiedBy()方法。

(4)或规格书(OrSpecification):对规格书进行或操作,实现isSatisfiedBy()方法。

(5)非规格书(NotSpecification):对规格书进行非操作,实现isSatisfiedBy()方法。

(6)业务规格书(BizSpecification):实现isSatisfiedBy()方法,对业务进行判断,一个类为一种判断方式,可进行扩展。

4 规格模式的通用写法

以下是规格模式的通用写法。

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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
java复制代码
public class Client {

public static void main(String[] args) {
//待分析的对象
List<Object> list = new ArrayList<Object>();
//定义两个业务规格书
ISpecification spec1 = new BizSpecification("a");
ISpecification spec2 = new BizSpecification("b");
//规格调用
for (Object o : list) {
if(spec1.and(spec2).isSatisfiedBy(o)){ //如果o满足spec1 && spec2
System.out.println(o);
}
}
}

//抽象规格书
interface ISpecification {
//候选者是否满足条件
boolean isSatisfiedBy (Object candidate) ;
//and操作
ISpecification and (ISpecification spec);
//or操作
ISpecification or (ISpecification spec);
//not操作
ISpecification not ();
}

//组合规格书
static abstract class CompositeSpecification implements ISpecification {
//是否满足条件由子类实现
public abstract boolean isSatisfiedBy (Object candidate) ;
//and操作
public ISpecification and (ISpecification spec) {
return new AndSpecification(this, spec);
}
//or操作
public ISpecification or(ISpecification spec) {
return new OrSpecification(this, spec);
}
//not操作
public ISpecification not() {
return new NotSpecification(this);
}
}

//与规格书
static class AndSpecification extends CompositeSpecification {
//传递两个规格书进行and操作
private ISpecification left;
private ISpecification right;

public AndSpecification(ISpecification left, ISpecification right) {
this.left = left;
this.right = right;
}

//进行and运算
public boolean isSatisfiedBy(Object candidate) {
return left.isSatisfiedBy(candidate) && right.isSatisfiedBy(candidate);
}
}


static class OrSpecification extends CompositeSpecification {
//传递两个规格书进行or操作
private ISpecification left;
private ISpecification right;

public OrSpecification(ISpecification left, ISpecification right) {
this.left= left;
this.right = right;
}

//进行or运算
public boolean isSatisfiedBy(Object candidate) {
return left.isSatisfiedBy(candidate) || right.isSatisfiedBy(candidate);
}
}

static class NotSpecification extends CompositeSpecification {
//传递两个规格书进行非操作
private ISpecification spec;

public NotSpecification(ISpecification spec) {
this.spec = spec;
}

//进行not运算
public boolean isSatisfiedBy(Object candidate) {
return !spec.isSatisfiedBy(candidate);
}
}

//业务规格书
static class BizSpecification extends CompositeSpecification {
//基准对象,如姓名等,也可以是int等类型
private String obj;
public BizSpecification(String obj) {
this.obj = obj;
}
//判断是否满足要求
public boolean isSatisfiedBy(Object candidate){
//根据基准对象判断是否符合
return true;
}
}
}

5 规格模式的优点

规格模式非常巧妙地实现了对象筛选功能,适合在多个对象中筛选查找,或者业务规则不适于放在任何已有实体或值对象中,而且规则变化和组合会掩盖对象的基本含义的情况。

6 规格模式的缺点

规格模式中有一个很严重的问题就是父类依赖子类,这种情景只有在非常明确不会发生变化的场景中存在,它不具备扩展性,是一种固化而不可变化的结构。一般在面向对象设计中应该尽量避免。

【推荐】Tom弹架构:30个设计模式真实案例(附源码),挑战年薪60W不是梦

本文为“Tom弹架构”原创,转载请注明出处。技术在于分享,我分享我快乐!如果本文对您有帮助,欢迎关注和点赞;如果您有任何建议也可留言评论或私信,您的支持是我坚持创作的动力。关注『 Tom弹架构 』可获取更多技术干货!

本文转载自: 掘金

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

对象池模式(Object Pool Pattern)

发表于 2021-11-25

本文节选自《设计模式就该这样学》

1 对象池模式的定义

对象池模式(Object Pool Pattern),是创建型设计模式的一种,将对象预先创建并初始化后放入对象池中,对象提供者就能利用已有的对象来处理请求,减少频繁创建对象所占用的内存空间和初始化时间。
一个对象池包含一组已经初始化并且可以使用的对象,可以在有需求时创建和销毁对象。对象池的用户可以从池子中取得对象,对其进行操作处理,并在不需要时归还给池子而非直接销毁。对象池是一个特殊的工厂对象,对象池模式就是单例模式加享元模式。

2 对象池模式的应用场景

对象池模式主要适用于以下应用场景。

(1)资源受限的场景。比如,不需要可伸缩性的环境(CPU\内存等物理资源有限),CPU性能不够强劲,内存比较紧张,垃圾收集,内存抖动会造成比较大的影响,需要提高内存管理效率, 响应性比吞吐量更为重要。

(2)在内存中数量受限的对象。

(3)创建成本高的对象,可以考虑池化。

补充:常见的使用对象池的场景有在使用Socket时的各种连接池、线程池、数据库连接池等。

3 对象池模式的UML类图

对象池模式的UML类图如下图所示。

file

由上图可以看到,对象池模式主要包含3个角色。

(1)对象池(ObjectPool):持有对象并提供取/还等方法。

(2)抽象池化对象(PooledObject):对池中对象的抽象。

(3)具体池化对象(ConcretePoolObject):对池中对象的封装,封装对象的状态和一些其他信息。

4 对象池模式的通用写法

以下是对象池模式的通用写法。

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
73
74
75
76
77
java复制代码
public class Client {

public static void main(String[] args) {
ObjectPool pool = new ObjectPool(10,50);
IPooledObject object = pool.borrowObject();
object.operation();
pool.returnObject(object);
System.out.println();
}

//抽象对象
interface IPooledObject {
void operation();
}
//具体对象
static class ConcretePoolObject implements IPooledObject {
public void operation() {
System.out.println("doing");
}
}

//对象池
static class ObjectPool {
private int step = 10; //当对象不够用的时候,每次扩容的数量
private int minCount;
private int maxCount;
private Vector<IPooledObject> returneds; //保存未借出的对象
private Vector<IPooledObject> borroweds; //保存已被借出的对象

//初始化对象池
public ObjectPool(int minCount,int maxCount){
borroweds = new Vector<IPooledObject>();
returneds = new Vector<IPooledObject>();

this.minCount = minCount;
this.maxCount = maxCount;

refresh(this.minCount);
}

//因为内部状态具备不变性,所以作为缓存的键
public IPooledObject borrowObject() {
IPooledObject next = null;
if(returneds.size() > 0){
Iterator<IPooledObject> i = returneds.iterator();
while (i.hasNext()){
next = i.next();
returneds.remove(next);
borroweds.add(next);
return next;
}
}else{
//计算出剩余可创建的对象数
int count = (maxCount - minCount);
//剩余可创建的数量大于单次固定创建的对象数
//则再初始化一批固定数量的对象
refresh(count > step ? step : count);
}
return next;
}

//不需要使用的对象归还重复利用
public void returnObject(IPooledObject pooledObject){
returneds.add(pooledObject);
if(borroweds.contains(pooledObject)){
borroweds.remove(pooledObject);
}
}

private void refresh(int count){
for (int i = 0; i < count; i++) {
returneds.add(new ConcretePoolObject());
}
}
}
}

对象池模式和享元模式的最大区别在于,对象池模式中会多一个回收对象重复利用的方法。所以,对象池模式应该是享元模式更加具体的一个应用场景。相当于先将对象从对象池中借出,用完之后再还回去,以此保证有限资源的重复利用。

5 对象池模式的优点

复用池中对象,消除创建对象、回收对象所产生的内存开销、CPU开销,以及跨网络产生的网络开销。

6 对象池模式的缺点

(1)增加了分配/释放对象的开销。

(2)在并发环境中,多个线程可能(同时)需要获取池中对象,进而需要在堆数据结构上进行同步或者因为锁竞争而产生阻塞,这种开销要比创建销毁对象的开销高数百倍。

(3)由于池中对象的数量有限,势必成为一个可伸缩性瓶颈。

(4)很难合理设定对象池的大小,如果太小,则起不到作用;如果过大,则占用内存资源高。

【推荐】Tom弹架构:30个设计模式真实案例(附源码),挑战年薪60W不是梦

本文为“Tom弹架构”原创,转载请注明出处。技术在于分享,我分享我快乐!如果本文对您有帮助,欢迎关注和点赞;如果您有任何建议也可留言评论或私信,您的支持是我坚持创作的动力。关注『 Tom弹架构 』可获取更多技术干货!

本文转载自: 掘金

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

C# XmlDocument读写xml

发表于 2021-11-25

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

C# 的XmlDocument类是 XML 文档的内存中表示形式。 它实现 W3C 级别1核心和核心 DOM 级别2。

XML 文档对象模型 (DOM)

XML 文档对象模型 (DOM) 类是 XML 文档的内存中表示形式。 DOM 使您能够以编程方式读取、处理和修改 XML 文档。 虽然 XmlReader 类也读取 XML,但它提供的是非缓存、仅正向的只读访问。 也就是说,使用 XmlReader 无法编辑属性值或元素内容,也无法插入和删除节点。 编辑是 DOM 的主要功能。 XML 数据在内存中表示是常见的结构化方法,尽管实际的 XML 数据在文件中时或从另一个对象传入时以线性方式存储。 以下是 XML 数据。

1
2
3
4
5
6
7
8
9
10
11
12
xml复制代码<?xml version="1.0"?>
<books>
<book>
<author>Carson</author>
<price format="dollar">31.95</price>
<pubdate>05/01/2001</pubdate>
</book>
<pubinfo>
<publisher>MSPress</publisher>
<state>WA</state>
</pubinfo>
</books>

XML 数据读入 DOM 结构中时如何构造内存。

image.png

使用XmlDocument构造XML

如上文档我们通过XmlDocument类来构建。

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
ini复制代码XmlDocument xmlDoc = new XmlDocument();
xmlDoc.CreateXmlDeclaration("1.0", "utf-8", "yes");
XmlElement xmlRootNode = xmlDoc.CreateElement("books");

XmlElement bookNode = xmlDoc.CreateElement("book");
XmlElement authorNode = xmlDoc.CreateElement("author");
authorNode.InnerText="Carson";
bookNode.AppendChild(authorNode);

XmlElement authorNode = xmlDoc.CreateElement("author");
authorNode.InnerText="Carson";
bookNode.AppendChild(authorNode);

XmlElement priceNode = xmlDoc.CreateElement("price");
authorNode.InnerText="31.95";
authorNode.SetAttribute("format","dollar");
bookNode.AppendChild(priceNode);

XmlElement pubdateNode = xmlDoc.CreateElement("pubdate");
pubdateNode.InnerText="05/01/2001";
bookNode.AppendChild(pubdateNode);
xmlRootNode.AppendChild(bookNode);


XmlElement pubinfoNode = xmlDoc.CreateElement("pubinfo");

XmlElement publisherNode = xmlDoc.CreateElement("publisher");
publisherNode.InnerText="MSPress";
pubinfoNode.AppendChild(publisherNode);

XmlElement stateNode = xmlDoc.CreateElement("state");
stateNode.InnerText="WA";
pubinfoNode.AppendChild(stateNode);
xmlRootNode.AppendChild(pubinfoNode);

使用XmlDocument初始化文档,然后CreateXmlDeclaration() 设置xml头。最后用AppendChild的方式将节点组织到xml结构中。

存储和获取xml文本

xmlDoc.Save(path) 方法传入要存入的文件路径Path就能直接将xml的文档保存到文件。如果要想直接获取XML 还要使用XmlWriter 将xml写入后获取文本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
ini复制代码XmlWriter xmlWriter = null;

XmlWriterSettings settings = new XmlWriterSettings();
//要求缩进
settings.Indent = true;
//设置encoding utf-8,默认将输出utf-16
settings.Encoding = new UTF8Encoding(false);
//设置换行符
settings.NewLineChars = Environment.NewLine;

MemoryStream ms = new MemoryStream();
try
{
xmlWriter = XmlWriter.Create(ms, settings);
xmlWriter.WriteStartDocument();
xmlDoc.WriteTo(xmlWriter);
}
finally
{
if (xmlWriter != null)
xmlWriter.Close();
ms.Close();
}
string xmlStr=Encoding.UTF8.GetString(ms.ToArray());

这样我们就能获取到xml的文本了,可以直接存入数据库字段。还有其它的方式读写xml,比如直接使用XmlWriter。下次在讲XmlWriter。

本文转载自: 掘金

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

Python爬虫实战,pyecharts模块,Python实

发表于 2021-11-25

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

前言

利用Python实现18年北上广深空气质量数据分析可视化。废话不多说。

让我们愉快地开始吧~

开发工具

Python版本: 3.6.4

相关模块:

requests模块

numpy模块

pandas模块

pyecharts模块;

以及一些Python自带的模块。

环境搭建

安装Python并添加到环境变量,pip安装需要的相关模块即可。

网站分析

北上广深AQI全年走势图

2017天津空气质量了

这里简单给大家科普一下有关AQI,PM2.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
python复制代码import time
import requests
from bs4 import BeautifulSoup

headers = {
    'User-Agent':'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36'
}
for i in range(1, 13):
    time.sleep(5)
    # 把1转换为01
    url = 'http://www.tianqihoubao.com/aqi/tianjin-2017' + str("%02d" % i) + '.html'
    response = requests.get(url=url, headers=headers)
    soup = BeautifulSoup(response.text, 'html.parser')
    tr = soup.find_all('tr')
    # 去除标签栏
    for j in tr[1:]:
        td = j.find_all('td')
        Date = td[0].get_text().strip()
        Quality_grade = td[1].get_text().strip()
        AQI = td[2].get_text().strip()
        AQI_rank = td[3].get_text().strip()
        PM = td[4].get_text()
        with open('air_tianjin_2017.csv', 'a+', encoding='utf-8-sig') as f:
            f.write(Date + ',' + Quality_grade + ',' + AQI + ',' + AQI_rank + ',' + PM + '\n')

数据获取成功

数据获取

先来看看天津

AQI全年走势图

天津AQI全年走势图

92.5是年均AQI值,从上面科普知识里可以知道,2017年天津整体空气质量只能是「良」中的下下等水平,与轻度污染近在咫尺。

AQI月均走势图

AQI月均走势图

从月均的走势图就能看出,1月的空气质量最差,8月的空气质量最好,当也并不是有多好,充其量也就是个「良」!

AQI季度箱形图

AQI季度箱形图

箱形图,显示一组数据分散情况资料的统计图。

数据里有最大值、最小值、中位数和两个四分位数。

这里可以看出,2017年天津的季度AQI均值差距不是很大。

但是一、二、四季度有明显的波动,空气质量有时会变得很差。

PM2.5全年走势图

PM2.5全年走势图

59.87是年均PM2.5值,已经远超过国家二级标准限值35了。

其实天津给我留下的印象就是天气经常灰蒙蒙,时常还会变点颜色,比如黄色~

一年下不了几次雨,及其干燥。所以那个最低值11。

PM2.5月均走势图

PM2.5月均走势图

和AQI的走势差不多,同样是1月最高,8月最低。

PM2.5季度箱形图

PM2.5季度箱形图

基本上四个季度都超标了,一年不超标的估计也就那么几次。

PM2.5指数日历图

PM2.5指数日历图1

PM2.5指数日历图2

PM2.5指数日历图3

日均PM2.5国家二级标准为75,从上面的热力图看,基本上轻度污染过半了。

天津全年空气质量情况

天津全年空气质量情况

「良」和「轻度污染」占了大头,「优」只能在角落里瑟瑟发抖,足以说明空气之差。

接下来看看北上广深

北上广深AQI全年走势图

北上广深AQI全年走势图

北上广深全年空气质量情况

北上广深全年空气质量情况

深圳几乎都是「优」和「良」,上海和广州和上面说的一样,北京的「优」已经不少了。

本文转载自: 掘金

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

PostgreSql 默认 SCHEMA 再探究

发表于 2021-11-25

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

为何建议为用户指定默认 SCHEMA

为了不用用户创建对象时,需要显示地指定 schema,如:

1
2
3
4
sql复制代码--不需要这样
CREATE TABLE em.emp(id int);
--可以这样
CREATE TABLE emp(id int);

PostgreSql中,所有新建的数据库都会默认自带一个 public 模式,所有用户均可在这个模式下创建管理自己的对象,且在数据库中创建表、视图等基本对象时,若没有指定模式,这些对象默认也会存放在 pubilc 模式下。(用户名与模式名同名除外)
详情参考:juejin.cn/post/698719…

上述情况发生的原因

上述原因在于 postgresql 数据库中的默认搜索路径:search_path。

1
2
3
4
5
sql复制代码oa=# show search_path;
search_path
-----------------
"$user", public
(1 row)

观察上述搜索路径可看到,用户连入数据库后首先会找寻跟自己用户名同名的 SCHEMA,若能找到,就它作为当前 schema,后续创建的所有对象都会存入到这个 schema 下。(前提是用户要有访问这个schema 的权限)

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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
sql复制代码--测试名字与schema名不同的用户,即使有访问 schema 的权限,连入时也不是当前 schema。
oa=# \dn
List of schemas
Name | Owner
--------+----------
em | postgres
op | postgres
public | postgres
rl | postgres
(4 rows)
oa=# \c - syd
You are now connected to database "oa" as user "syd".
oa=> select current_schema;
current_schema
----------------
public
(1 row)

oa=# grant USAGE on schema rl to syd;
GRANT
oa=# \c - syd
You are now connected to database "oa" as user "syd".
oa=> select current_schema;
current_schema
----------------
public
(1 row)

--测试与 schema 同名用户,需要有访问该 schema 的权限,连入时才为当前 schema。(两种授权访问 schema 模式的方法)
oa=> \c - em
You are now connected to database "oa" as user "em".
oa=> select current_schema;
current_schema
----------------
public
(1 row)

oa=> \dn
List of schemas
Name | Owner
--------+----------
em | postgres
op | postgres
public | postgres
rl | postgres
(4 rows)

--1)更改 schema 的 owner 为这个用户,用户自然就具有了访问 schema 的权限
oa=> \c - postgres
You are now connected to database "oa" as user "postgres".
oa=# alter schema em owner to em;
ALTER SCHEMA
oa=# \dn
List of schemas
Name | Owner
--------+----------
em | em
op | postgres
public | postgres
rl | postgres
(4 rows)
oa=# \c - em
You are now connected to database "oa" as user "em".
oa=> select current_schema;
current_schema
----------------
em
(1 row)

--2)直接授予用户访问 schema 的权限
oa=# \dn
List of schemas
Name | Owner
--------+----------
em | em
op | postgres
public | postgres
rl | rl
(4 rows)

oa=# \c - op
You are now connected to database "oa" as user "op".
oa=> select current_schema;
current_schema
----------------
public
(1 row)

oa=> \c - postgres
oa=# grant USAGE on schema op to op;
GRANT
oa=# \c - op
You are now connected to database "oa" as user "op".
oa=> select current_schema;
current_schema
----------------
op
(1 row)

如何指定上述情况中普通用户 syd 的默认 schema

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
sql复制代码oa=# alter user syd set search_path to rl;
ALTER ROLE
oa=# \c - syd
You are now connected to database "oa" as user "syd".
oa=> \dn
List of schemas
Name | Owner
--------+----------
em | em
op | postgres
public | postgres
rl | rl
(4 rows)

oa=> select current_schema;
current_schema
----------------
rl
(1 row)

总结

1
2
3
4
5
6
7
8
9
10
11
12
sql复制代码--设置 apply_owner 用户连入任何数据库,当前默认 schema 都为 apply(数据库中没有这 schema 时,默认 schema 为 public)   
alter user apply_owner set search_path to apply;
--设置任何用户连入 chis 数据库,当前默认 schema 都为 apply
alter database chis set search_path to apply;
--用户名与 schema 同名,且用户具有访问改 schema 的权限,用户连入数据库时,默认即为当前 schema
create database chis;
\c chis create schema apply;
create user apply password '11111';

grant USAGE on schema apply to apply;
或
alter schema apply owner to apply;

本文转载自: 掘金

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

API 编排的应用及痛点

发表于 2021-11-25

作者:戴泽军

前言

随着微服务架构成为业界主流,API 网关作为访问微服务集群内所有 API 的唯一出入口,地位显得尤为重要。

集群内微服务功能拆分越来越细,对后端而言,模块在独立性、复用性、可维护性方面的优势不言而喻。但对前端而言,复杂性却随之而来。一个前端页面,往往需要从数个甚至数十个 API 中请求数据,而这些 API,很可能还存在如下异构特性:托管 host 不同、实现语言不同、调用方式不同。

面对这样的难题,API 网关的需求应运而生:统一出口、隐藏实现、安全控制、流量控制、负载均衡、权限控制、API 管理等等。这些问题及其解决方案将在后续的文章中一一介绍。本期我们重点讨论 API 网关的另一个重要功能:API 编排。

API 编排的应用

所谓 API 编排,就是通过 API 网关实现一种机制,可以灵活调用和组装后端原生 API,使前端能够根据业务需求,定制化页面所需的聚合 API。其地位和作用,可相较于“Shell 之于 *nix”。

一个典型的 API 编排应用案例如下:

API 编排应用.png

图注:图中方框大小示意调用时数据量大小

可以看出,与后端微服务 API 以“高内聚”为目标不同,聚合 API 以业务需求为导向,通过简单组合已有的原生 API,在后端代理调用多个 API,并对输出数据进行重新剪裁组装。

API 编排的优势

与前端直接调用多个异构的后端 API 相比,聚合 API 具有很明显的优势:

  • 简化前端逻辑,一次 API 调用即可获取所有所需数据;
  • 减少传输数据量,仅向前端发送需要展示的数据;
  • 灵活的动态组合扩展特性,后端只需要实现“原子 API”,把业务需求交给消费方去自助配置,组合扩展;
  • 同化调用方法,由 API 编排服务兼容异构实现的后端 API 的调用过程;
  • 统一的身份认证和权限管理;
  • 向前端隐藏第三方 API 的身份认证过程,由 API 编排服务兼容不同平台的 API 认证方法。

API 编排的痛点

面向没有编程基础的普通用户服务

由于 API 编排具有“用户自助”的特性,注定了其用户“既是生产者又是消费者”。而任何网络平台的用户,都必须假定为“没有编程基础”,即面向没有编程经验的普通用户服务。

功能需要覆盖完备的编程元素

API 编排需要构造多个 API 调用,捕获和剪裁输出数据,同时也可能存在分支和根据条件重复处理等需求。

我们的编程启蒙老师一定都讲过,编程无非就是顺序+选择+循环。可见,编程所需所有元素,在 API 编排中皆有需求。然而,在我们的用户是没有编程基础的普通用户的情况下,如何让他们理解和描述要进行的操作,是另一个难题。

没有现成的编程语言可用

站在程序员的角度,在各种高级编程语言百家争鸣的今天,描述和实现“ API 编排”需求的方法有千万种。

静态语言有 C、C++、C#、JAVA、GO 等供我们选择,动态脚本语言有 Python、Lua、Perl、JavaScript、Ruby、Lisp 等多种选择。可以说任意组合一门静态语言+一门动态语言,都可以完美胜任我们的“动态 API 编排”任务。然而,当我们的用户被定义为“没有编程基础”,该问题就开始变得扑朔迷离。

后端 API 实现上的异构特性

由于微服务拆分的原则是让每个服务足够独立,对服务的实现语言和使用的技术,并不会做严格的限制,所以微服务天生就有异构的特性。

如果是同一个 team 开发各个子服务,可能会在 API 提供方式、调用方法上做一些简单约定。如果是由不同的 team 开发这些子服务,甚至还会存在 HTTP/RPC、RESTful/非 REST 这些可选项。如果需要兼容第三方 API,则还会存在编程语言差异,使用的技术差异等。

因此,对于一个对兼容性有足够考虑的 API 编排系统而言,承认和处理后端 API 的异构问题,是必然绕不过去的弯。

原子化的描述 API 的调用方法及构造输入输出参数

虽然 API 编排不是直接由程序员编写代码来构造一个 HTTP/RPC 调用来完成 API 访问,但其最终要实现的效果与前者高度一致。由于后端 API 天生的异构特性,使我们必须提供一种准确且易懂的描述方式,让用户告诉我们如下问题的答案:

  • API 的访问地址在哪?采用何种调用协议?
  • 是否需要身份认证?采用什么方法构造这个身份认证参数?构造身份认证参数的 API 私钥是什么?
  • 输入数参数从哪里来?放到哪里去?是否需要做简单的算术运算?
  • 输出数据是否需要进行加工?多个 API 的输出如何重新组装输出?

因此,描述和实现构造 API 调用参数的过程和方法,成为设计 API 编排系统中最关键的一环。

编排第三方 API 的身份认证问题

由于一般对外提供 API 服务的系统,都会加入身份认证功能,来保证 API 不会被非法调用,以此保证服务器安全与稳定。对于需要兼容第三方 API 的 API 编排系统而言,需要采用一种通用的描述方法,让实现 API 的第三方可以准确的描述自己实现的 API 所使用的身份认证方法。

常见的身份认证方法有:OIDC、 JWT、 bearer Tokens、Basic Auth、Signature 等等。其中 Signature 认证方法只是使用“签名”认证方式的一种统称。实际上采用何种算法,使用什么步骤来构造这个“签名字符串”,都需要有统一的方法来详细描述。

说明:
常见的“签名”构造方法有:参数排序方法、追加字符串、计算 HASH 值、计算 HMAC HASH、AES/RSA 加密解密、hex/base64/url 编码解码等等。

由于第三方所使用的身份认证方法的多样性,且没有统一的标准,因此对 API 编排系统而言,如何原子化的定义和描述这些身份认证方法,也是一个不容忽视的大课题。

API 及 API 私钥的权限问题

API 私钥(如:登录网络平台的用户名密码)是用户访问第三方 API 平台的唯一身份证明数据,对用户数据安全有着至关重要的作用。因此,用户是不会轻易将此数据泄露给任何不可信的第三方的。API 编排需要代理用户向第三方 API 服务器发起 API 调用请求,所以必须由用户提供此私钥才能完成该操作。

那么,问题的核心即变为,我们需要设计一套严密的安全体系,让用户信任我们的系统,将 API 私钥托管在平台是安全可信的。安全托管这些 API 私钥的必要环节包括:加密上传,加密存储,任何第三方用户使用需要授权,后端透明使用,内容对任何用户不可见。

总结

本期简单介绍了 API 网关在微服务架构下的必要性。重点介绍了 API 网关的核心功能,API 编排的应用范围和设计上面临的诸多难题和挑战。由于 API 编排需要面对无编程基础的普通用户服务,使得设计 API 编排服务变得像实现“没有编程语言”的编程体验一样有趣。

后期文章,我们将继续探讨 API 网关和 API 编排设计和实现上的一些思考和探索。敬请期待。

关于全象云

全象云是青云科技自主研发的工具和平台,是基于云原生、解耦的、可插拔的开放生态系统,用于辅助构建企业各类数字化应用。

平台目前提供云上无代码和低代码两种应用开发模式,屏蔽了技术的复杂度。支持可视化设计器,让开发人员和业务用户能够通过简单的拖拽、参数配置等方式快速完成应用开发。同时集成了身份认证能力、容器 DevOps 能力,支持企业存量业务与全象云业务融合。平台还包含丰富的开发接口和强大的插件机制,开发者可根据需要不断拓展平台的应用能力。

全象云的愿景是:在企业生产经营的各个象限、各个环节提供软件构件或支持服务。

本文转载自: 掘金

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

1…192193194…956

开发者博客

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