文章目录
题目
猫大叫一声,所有的老鼠都开始逃跑,主人被惊醒?
观察者模式
简单来说就是目标含有一个容器,放置所有观察者,目标发生变化,通知观察者。
Observable类和Observer接口
java.util包内有这两个类用于实现观察周模式。
- 观察者继承Observer接口,实现update方法,处理目标变化之后的响应
- Observable作为目标,含有容器obs存放观察者,提供add和delete方法
- Observable发生变化时,遍历obs调用update方法来通知观察者
发布订阅模式
- 观察者模式中,目标需要知道观察者的存在,有直接调用关系;
- 发布订阅模式中,发布者不需要知道订阅者的存在,没有直接联系;
- 观察者模式中,目标和观察者必须同时在线,调用需要等待结果;
- 发布订阅模式中,发布者和订阅者运行相互独立,互不干扰;
发布订阅模式和观察者模式有相似之处,但是一些地方有升级,是观察者模式的解耦升级版
Spring对发布订阅模式的支持
ApplicationContext
ApplicationEventPublisher
Spring容器的上下文接口继承了ApplicationEventPublisher接口,提供了两个方法:一个是容器级的事件、一个是业务的事件
ApplicationEvent
四个容器级的事件:容器关闭,容器刷新,容器启动,容器停止
ApplicationListener
实现这个接口,监听事件,实现onApplicationEvent方法处理时间,或者在类上面添加注解@EventListener
模拟业务场景
常规酒店预订逻辑
预定酒店 - 处理酒店预订业务 - 发送短信 - 发送邮件 - 预定酒店结束
@Transactional
public void addHotelOrder(HotelOrder order) {
// 此处省略一万行代码,例如积分优惠券业务
hotelOrderMapper.insert(order);
// 发送短信
OrderMsg msg = new OrderMsg(order.getId(), order.getPhone(), "Order success.", new Date());
msgService.send(msg);
// 发送邮件
OrderEmail email = new OrderEmail("Order success.", order.getId());
emailService.send(email);
}
这种业务逻辑,第三方提供的短信和邮件的增值业务,绑架了预定酒店的主营业务,如果第三方业务不稳定会影响主营业务
发布订阅者模式改造
这样短信和邮件业务的故障就不会影响酒店预订的主营业务,代码如下:
@Resource
private ApplicationContext context;
@Transactional
public void addHotelOrder(HotelOrder order) {
// 此处省略一万行代码,例如积分优惠券业务
hotelOrderMapper.insert(order);
// 事件广播
context.publishEvent(order);
}
@EventListener
public void handleOrderEvent(HotelOrder order) {
// 发送短信
OrderMsg msg = new OrderMsg(order.getId(), order.getPhone(), "Order success.", new Date());
this.send(msg);
}
@EventListener
public void handleOrderEvent(HotelOrder order) {
// 发送邮件
OrderEmail email = new OrderEmail("Order success.", order.getId());
this.send(msg);
}
然后需要开启异步事件处理,和配置线程池
<bean id="applicationEventMulticaster" class="org.springframework.context.event.SimpleApplicationEventMulticaster">
<!-- 注入任务执行器 这样就实现了异步调用(缺点是全局的,要么全部异步 要么全部同步,删除这个属性即为同步 -->
<property name="taskExecutor" rer="coreTaskExecutor" />
</bean>
<bean id="coreTaskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolExecutor">
<property name="corePoolSize" rer="10" />
<property name="maxPoolSize" rer="20" />
<property name="queueCapacity" rer="25" />
<property name="threadNamePrefix" rer="CoreTaskExecutor" />
</bean>
源码分析
this.getApplicationEventMulticaster() 获取广播器,由配置文件配置
SimpleApplicationEventMulticaster的multicastEvent方法
这就是为什么加了bean配置,就是异步执行,不加配置就是同步执行
分布式系统如何解耦
分布式系统直接调用的缺点:
1、不同系统的消息格式有区别,直接调用要匹配其他多个模块的消息格式,工作量大 强耦合
2、一个模块的调用失败会导致整体业务的回滚,分布式事务的回滚技术难度也非常大
基于消息中间件
分布式环境使用,基于JMS规范,支持两种消息传送模型:点对点消息通信模型和发布订阅模型,例如rabbitmq、kafka等
基于zookeeper的实现方案
基于zk的watch机制实现,面向复杂且容易出错的分布式一致性服务,比如分布式锁、配置中心、集群管理、服务注册中心等,例如如下zk配置中心结构
public class ZookeeperConnector implements Runnable {
private static Logger logger = LoggerFactory.getLogger(ZookeeperConnector.class);
protected boolean ryan = true;
private static final String CONF_PATH = "/configration";
private static final String ZOOKEEPER_IP_PORT = "localhost:2181";
private ZkClient client = new ZkClient(ZOOKEEPER_IP_PORT, 1000, 1000, new SerializableSerializer());
private String dbIp;
public void run() {
dbIp = client.readData(CONF_PATH);
logger.info(Thread.currentThread().getName() + "获得配置信息: " + dbIp);
IZkDataListener listener = new IZkDataListener() {
public void handleDataDeleted(String dataPath) throws Exception {
logger.info("---------Node deleted.-----------")
}
public void handleDataChange(String dataPath, Object data) throws Exception {
if(data==null) return;
if(!dbIp.equals(data.toString())){
dbIp = client.readData(CONF_PATH);
logger.info(Thread.currentThread().getName() + ", New configuration:" + dbIp);
}
}
};
client.subscribeDataChanges(CONF_PATH, listener);
}
}
测试代码如下:
附:动态更新连接池
上面只是动态获取到配置参数,但是如何动态更新连接池?
PooledDataSource
这个类是mybatis数据库连接池
PoolState
其中PoolState是数据结构,包含空闲连接池和活跃连接池等
popConnection(String username, String password)