观察者模式
没有营养的话:
在学习观察者模式的时候
观察者模式分为两大部分
被观察者和观察者
对于这两大部分,可以在生活中找到很多对应的关系,视观察的对象对场景有很大的不一样
比如,被观察者是微博的大V,那么观察者就是关注他的粉丝,大V发出动态,他的粉丝就会收到反馈
被观察者是一名嫌疑犯的时候,那么观察者就是警察,嫌疑犯有任何可疑的动静的时候,警察都可以收到反馈
之前在学习观察者模式的时候,容易和消息中间件的那种生产订阅模式混淆,其实他们不一样
和生产订阅的那种模式很像但是又没有那种模式那么解耦
观察者模式由于只有两个部分,所以对于观察者的维护,就在被观察者的对象中,会维护一个观察者的集合
而生产消费模式中,生产只负责生产,消费只负责消费,中间的需求关系由另外的Broker进行维护,实现真正意义上的解耦
观察者模式的实现
结构
- 观察者接口
- 被观察对象的接口
- 观察者接口的实现
- 被观察对象接口的实现
- 主类测试
观察者的接口
package it.luke.observer;
/**
* 观察者接口
*/
public interface ObserverInter {
/**
* 针对某个被观察对象的消息更新
* @param message
* @param name
*/
void update(String message,String name);
}
被观察对象的接口
package it.luke.observer;
/**
* 观察的目标接口
*/
public interface TargetInter {
// 添加观察者
TargetInter addObserver(ObserverInter Observer);
// 移除观察者
void removeObserver(ObserverInter Observer);
// 通知观察者
void notice(String message);
}
观察者的实现
package it.luke.observer;
/**
* 观察者1
*/
public class Observer_v1 implements ObserverInter {
private String name = "观察者1";
private String message;
//用于主类调用的时候观察反馈
//这里的参数name是被观察者的name
@Override
public void update(String message, String name) {
this.message = message;
System.out.println(this.name + "收到反馈:" + name + "更新了事件:" + message);
}
}
/**
* 观察者2
*/
class Observer_v2 implements ObserverInter {
private String name = "观察者2";
private String message;
@Override
public void update(String message, String name) {
this.message = message;
System.out.println(this.name + "收到反馈:" + name + "更新了事件:" + message);
}
}
/**
* 观察者3
*/
class Observer_v3 implements ObserverInter {
private String name = "观察者3";
private String message;
@Override
public void update(String message, String name) {
this.message = message;
System.out.println(this.name + "收到反馈:" + name + "更新了事件:" + message);
}
}
主类测试
package it.luke.observer;
public class MainTest {
public static void main(String[] args) {
TargetImpl target = new TargetImpl();
Observer_v1 observer_v1 = new Observer_v1();
Observer_v2 observer_v2 = new Observer_v2();
Observer_v3 observer_v3 = new Observer_v3();
//添加观察者
target.addObserver(observer_v1).addObserver(observer_v2).addObserver(observer_v3);
//模拟场景,三名观察者观察着对象,接收对象的反馈信息
//场景1:嫌疑犯伸出他罪恶的手,企图从他人的包里窃取金钱!!!
target.notice("嫌疑犯伸出他罪恶的手,企图从他人的包里窃取金钱!!!");
}
}
观察者1收到反馈:target更新了事件:嫌疑犯伸出他罪恶的手,企图从他人的包里窃取金钱!!!
观察者2收到反馈:target更新了事件:嫌疑犯伸出他罪恶的手,企图从他人的包里窃取金钱!!!
观察者3收到反馈:target更新了事件:嫌疑犯伸出他罪恶的手,企图从他人的包里窃取金钱!!!
Process finished with exit code 0
可疑看到,当我们通过调用被观察者对象的notice()方法进行信息刷新的时候,由维护着观察者集合的被观察者调用update()方法,进行信息的反馈
除了我们自己的实现的观察者模式,java还有自己实现的观察者模式
java 的观察者的接口
/*
* Copyright (c) 1994, 1998, Oracle and/or its affiliates. All rights reserved.
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
package java.util;
/**
* A class can implement the <code>Observer</code> interface when it
* wants to be informed of changes in observable objects.
*
* @author Chris Warth
* @see java.util.Observable
* @since JDK1.0
*/
public interface Observer {
/**
* This method is called whenever the observed object is changed. An
* application calls an <tt>Observable</tt> object's
* <code>notifyObservers</code> method to have all the object's
* observers notified of the change.
*
* @param o the observable object.
* @param arg an argument passed to the <code>notifyObservers</code>
* method.
*/
void update(Observable o, Object arg);
}
观察者的实现类
package it.luke.observer.java_observer;
import java.util.Observable;
import java.util.Observer;
public class Oberver_v1 implements Observer {
private String name = "观察者1";
@Override
public void update(Observable o, Object arg) {
Target o1 = (Target) o;
String TarName = o1.getName();
System.out.println(name+"收到"+TarName+"反馈:"+arg.toString());
}
}
观察对象的接口(主要的更新方法)
public void notifyObservers(Object arg) {
/*
* a temporary array buffer, used as a snapshot of the state of
* current Observers.
*/
Object[] arrLocal;
synchronized (this) {
/* We don't want the Observer doing callbacks into
* arbitrary code while holding its own Monitor.
* The code where we extract each Observable from
* the Vector and store the state of the Observer
* needs synchronization, but notifying observers
* does not (should not). The worst result of any
* potential race-condition here is that:
* 1) a newly-added Observer will miss a
* notification in progress
* 2) a recently unregistered Observer will be
* wrongly notified when it doesn't care
*/
if (!changed)
return;
arrLocal = obs.toArray();
clearChanged();
}
for (int i = arrLocal.length-1; i>=0; i--)
((Observer)arrLocal[i]).update(this, arg);
}
其它的方法和自定义的差不多,多了一些校验
观察对象的实现类
package it.luke.observer.java_observer;
import java.util.Observable;
public class Target extends Observable {
private String name ="嫌疑犯";
public String getName() {
return name;
}
@Override
protected synchronized void setChanged() {
super.setChanged();
}
}
重写setChanged方法主要是java内置的更新会进行一个是否更新的判断,而且是protected修饰的,所以可疑通过重写的方式,手动调用来进行更新.
了解了观察者模式的大概之后,便可以以这个基础来学习用法更为广泛的生产订阅模式
这里引用大数据最常用的消息中间件(Kafka)不仅高吞吐量,而且快,支持分布式
在学习Kafka之前,同样的为了可以更好的看到代码的走向,建议将Kafka的源码拉下来构建
- 拉取Kafka源码,依旧是建议去码云上拉,比较是国内的,速度更快(kafka源码)
- 准备构建工具 gradle
- 构建(这里我采用的是idea,直接将项目导入idea,可以适当的修改build.gradle 中的仓库位置,减少构建时间)
显示成功;
验证:
平时我们在启动kafka的时候是在linux中使用命令行的形式,启动的kafka-server
这里主要是调用了kafka源码中的core模块的中kafka启动类,通过传入配置参数(config/server.properites)进行启动
kafka默认依赖于zookeeper,
在启动kafka之前,需要启动zookeeper,而在kafka源码中自带了zookeeper组件
可以进入到源码目录中,通过cmd的模式–>bin\zookeeper-server-start.sh config\zookeeper.properties
正常启动之后,默认开启了localhost:2181作为zookeeper的端口
接下来可以启动kafkaserver了—>以run configuration 的形式启动(配置参数见上方)
画面进入这里,没有报错,说明启动成功了,这里可以通过调用官方提供的样例代码来执行,看看是否构建完成
可以直接执行,如果报错,可以通过cmd查看你的topic是否存在,因为他的默认提供了三个测试的topic(topic1,topic2,topic3),如果不存在,可以通过命令行的形式创建topic
bin\windows\kafka-topics.bat --create --topic topic1 --replication factor 1 --partitions 1 --zookeeper localhost:2181
执行创建对应的topic
创建完之后继续执行
public void run() {
int messageNo = 1;
while (true) {
String messageStr = "Message_" + messageNo;
long startTime = System.currentTimeMillis();
if (isAsync) { // Send asynchronously
producer.send(new ProducerRecord<>(topic,
messageNo,
messageStr), new DemoCallBack(startTime, messageNo, messageStr));
} else { // Send synchronously
try {
producer.send(new ProducerRecord<>(topic,
messageNo,
messageStr)).get();
System.out.println("Sent message: (" + messageNo + ", " + messageStr + ")");
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
++messageNo;
}
producer 调用run方法,向指定的topic发送了自己模拟的数据
然后又调用了consumer进行消费,我们可以在控制台看到对应的输出
到这里说明我们的源码已经构建完毕
待补充…
1.Kafka 通过C\S架构,将客户端和服务端分离开来,其中涉及到的client访问服务端的线程并发问题,源码架构采用了
Reacotr模型(待补充…)
2.Kafka 服务端的启动原理(待补充…)
3.kafka 客户端的连接注册原理(待补充…)
4.kafka 生产消费原理(待补充…)
Reacotr模型主要分为三个角色
Reactor:把IO事件分配给对应的handler处理
Acceptor:处理客户端连接事件
Handler:处理非阻塞的任务
传统阻塞IO模型的不足
1:每个连接都创建一个线程去提供处理,如果遇到高并发情况下,创建的线程过多,会导致资源的浪费
2:如果采用阻塞的方式,连接建立之后,如果当前线程没有数据可读,线程便会阻塞在当前线程上,造成资源浪费
参考数据库连接池的案例,采用线程池的思想,可以有效的缓解高并发下,创建线程过多的问题
线程复用,如果当前无数据,会回收线程,或者一个线程处理多个连接,可以有效的避免线程占用带来的资源浪费.
Reactor线程模型的思想就是基于IO复用和线程池的结合