消息中间件选型标准
中间件的功能典型的消息中间件主要包含如下几个功能:
消息接收
消息分发
消息存储
消息读取
概念模型抽象的消息中间件模型包含如下几个角色:
发送者和接收者客户端(Sender/Receiver Client);
代理服务器(Broker Server),它们是与客户端代码直接交互的服务端代码;
消息交换机(Exchanger),接收到的消息一般需要通过消息交换机(Exchanger)分发到具体的消息队列中;
消息队列,一般是一块内存数据结构或持久化数据。
如下图所示:
选型标准大致有以下几点
1、性能性能主要有两个方面需要考虑:吞吐量(Throughput)和响应时间(Latency)。 不同的消息队列中间件的吞吐量和响应时间相差甚远,对于同一种中间件,不同的配置方式也会影响性能。
配置主要有以下几种:
是否需要确认机制,即写入队列后,或从队列读取后,是否需要进行确认。确认机制对响应时间的影响往往很大。
能否批处理,即消息能否批量读取或者写入。批量操作可以大大减少应用程序与消息中间件的交互次数和消息传递量,大大提高吞吐量。
能否进行分区(Parti ...
分布式队列编程优化
缓存优化(接收请求和处理之间)处于“处理-转发”模式下运行的生产者往往被设计成请求驱动型的服务,即每个请求都会触发一个处理线程,线程处理完后将结果写入分布式队列。但是如果队列不可用,那么生产者的处理线程就会产生堆积,则会导致以下两个问题:
系统可用性降低。由于每个线程都需要一定的内存开销,线程过多会使系统内存耗尽,甚至可能产生雪崩效应导致最终完全不可用。
信息丢失。为了避免系统崩溃,一般会给请求驱动型服务设置一个处理线程池,设置最大处理线程数量。这是一种典型的降级策略,目的是为了防止系统崩溃。但是,后续的请求会因为没有处理线程而被迫阻塞,最终可能产生信息丢失。
解决思路来自CAP理论,即通过降低一致性来保证可用性。具体如下:
生产者接收线程在收到请求之后第一时间不去处理,直接将请求缓存在内存中(牺牲一致性),而在后台启动多个处理线程从缓存中读取请求、进行处理并写入分布式队列。
与线程所占用的内存开销相比,大部分的请求所占内存几乎可以忽略。通过在接收请求和处理请求之间增加一层内存缓存,可以大大提高系统的处理吞吐量和可扩展性。这个方案本质上是一个内存生产者消费者模型。
批量写入优化(处 ...
Java的NIO
常见I/O模型对比所有的系统I/O都分为两个阶段:等待就绪和操作。
比如读函数,分为等待系统可读和真正的读;同理,写函数分为等待网卡可以写和真正的写。
其中,等待就绪的阻塞是不使用CPU的,是在“空等”;而真正的读写操作的阻塞是使用CPU的,真正在”干活”,而且这个过程非常快,属于memory copy,可以理解为基本不耗时。
具体例子以socket.read()为例子:
传统的BIO里面socket.read(),如果TCP RecvBuffer里没有数据,函数会一直阻塞,直到收到数据,返回读到的数据。
对于NIO,如果TCP RecvBuffer有数据,就把数据从网卡读到内存,并且返回给用户;反之则直接返回0,永远不会阻塞。
最新的AIO(Async I/O)里面会更进一步:不但等待就绪是非阻塞的,就连数据从网卡到内存的过程也是异步的。
BIO里用户最关心“我要读”,NIO里用户最关心”我可以读了”,在AIO模型里用户更需要关注的是“读完了”。
NIO一个重要的特点是:socket主要的读、写、注册和接收函数,在等待就绪阶段都是非阻塞的,真正的I& ...
命令模式
命令模式将请求(命令)封装为一个对象,这样可以使用不同的请求参数化其他对象(将不 同请求依赖注入到其他对象),并且能够支持请求(命令)的排队执行、记录日志、撤销等 (附加控制)功能。
一些编程语言不支持传递函数,但是我们将函数封装在对象中,然后传递对象,就可以达到同样的效果。
参考《设计模式之美》
备忘录模式
备忘录模式,也叫快照(Snapshot)模式,在不违背封装原则的前提下,捕获一个对象的内部状态,并在该对象之外 保存这个状态,以便之后恢复对象为先前的状态。
这个模式包含两部分:一部分是,存储副本以便后期恢复。另一部分是,要在不违背封装原则的前提下,进行对象的备份和恢复。
一个demo:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152public class InputText { private StringBuilder text = new StringBuilder(); public String getText() { return text.toString(); } public void append(String input) { text.append(input); } public Snapshot cre ...
访问者模式
概念访问者模式:允许一个或者多个操作应用到一组对象上,解耦操作和对象本身。
使用场景一般来说,访问者模式针对的是一组类型不同的对象(PdfFile、PPTFile、WordFile)。不 过,尽管这组对象的类型是不同的,但是,它们继承相同的父类(ResourceFile)或者实 现相同的接口。在不同的应用场景下,我们需要对这组对象进行一系列不相关的业务操作 (抽取文本、压缩等),但为了避免不断添加功能导致类(PdfFile、PPTFile、WordFile) 不断膨胀,职责越来越不单一,以及避免频繁地添加功能导致的频繁代码修改,我们使用访问者模式,将对象与操作解耦,将这些业务操作抽离出来,定义在独立细分的访问者类 (Extractor、Compressor)中。
为什么支持双分派的语言不需要访问者模式?Single Dispatch,指的是执行哪个对象的方法,根据对象的运行时类型来决定;执行对象的哪个方法,根据方法参数的编译时类型来决定。
Double Dispatch指的是执行哪个对象的方法,根据对象的运行时类型来决定;执行对象的哪个方法,根据方法参数的运行时类型来决定。
Singl ...
迭代器模式(下)
如何实现一个支持“快照”功能的迭代器?
所谓“快照”,指我们为容器创建迭代器的 时候,相当于给容器拍了一张快照(Snapshot)。之后即便我们增删容器中的元素,快照 中的元素并不会做相应的改动。而迭代器遍历的对象是快照而非容器,这样就避免了在使用 迭代器遍历的过程中,增删容器中的元素,导致的不可预期的结果或者报错。
方案一在迭代器类中定义一个成员变量 snapshot 来存储快 照。每当创建迭代器的时候,都拷贝一份容器中的元素到快照中,后续的遍历操作都基于这 个迭代器自己持有的快照来进行。
12345678910111213141516171819public class SnapshotArrayIterator<E> implements Iterator<E> { private int cursor; private ArrayList<E> snapshot; public SnapshotArrayIterator(ArrayList<E> arrayList) { thi ...
迭代器模式(上)
迭代器模式的原理和实现迭代器模式(Iterator Design Pattern),也叫作游标模式(Cursor Design Pattern)。
迭代器是用来遍历容器的,所以,一个完整的迭代器模式一般会涉及容器和容器迭代器两部 分内容。为了达到基于接口而非实现编程的目的,容器又包含容器接口、容器实现类,迭代 器又包含迭代器接口、迭代器实现类。
一个demo:
123456// 接口定义方式一public interface Iterator<E> { boolean hasNext(); void next(); E currentItem();}
123456789101112131415161718192021222324252627282930313233343536373839404142434445public class ArrayList<E> implements Iterator<E> { private int cursor; private ArrayList< ...
HashMap
hash方法下面是jdk8中的hash方法:
1234static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);}
该方法的作用:将 key 的 hashCode 值进行处理,得到最终的哈希值。
如下操作:
12HashMap<String, String> map = new HashMap<>();map.put("a", "b");
其中put的源码如下:
123public V put(K key, V value) { return putVal(hash(key), key, value, false, true);}
hash 方法的作用HashMap 的底层是通过数组的形式实现的,初始大小是 16,在添加第一个元素的时候,需要通过键的哈希码在大小为 16 的数组中确定一个位置,而 ...
职责链模式(下)
职责链模式常用在框架的开发中,为框架提供扩展点,让框架的使用者在不修改框架源码的情况下,基于扩展点添加新的功能。实际上,更具体点来说,职责链模式最常用来开发框架的过滤器和拦截器。
Servlet Filter
其中核心设计代码如下:
1234567891011121314151617181920212223242526272829303132public final class ApplicationFilterChain implements FilterChain { private int pos = 0; // 当前执行到了哪个filter private int n; // filter的个数 private ApplicationFilterConfig[] filters; private Servlet servlet; @Override public void doFilter(ServletRequest request, ServletResponse response) { ...