1 RabbitMQ
1.1 介绍
1.1.1 基本概念
-
Broker:简单来说就是消息队列服务器实体。
-
Binding:绑定,它的作用就是把exchange和queue按照路由规则绑定起来。
-
Routing Key:路由关键字,exchange根据这个关键字进行消息投递。
-
vhost:虚拟主机,一个broker里可以开设多个vhost,用作不同用户的权限分离。
-
producer:消息生产者,就是投递消息的程序。
-
consumer:消息消费者,就是接受消息的程序。
-
channel:消息通道,在客户端的每个连接里,可建立多个channel,每个channel代表一个会话任务。
1.1.2 三种类型exchange
-
direct :通过routing key
确定消息路由到具体的队列,系统存在一个默认的exchange
,名字是空串,消息路由时,根据routing key。 -
topic :通过* 和 # 通配符来匹配,* (star) can substitute for exactly one
word,# (hash) can substitute for zero or more words。 -
fanout:发送消息到所有队列,忽略 routing_key
1.1.3 集群高可用
为了保证集群的高可用,一般会使用镜像队列,镜像队列由一个master,多个slave构成,ha-mode有三种:
-
all:镜像到所有节点
-
exactly:镜像到指定节点数
-
nodes:镜像到指定节点
1.2 使用
RabbitMQ 使用的版本如下:
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>4.2.0</version>
</dependency>
1.2.1 创建TCP连接
ConnectionFactory factory = new ConnectionFactory();
connectionFactory.setAutomaticRecoveryEnabled(true);// 故障自动重连
factory.setUsername(userName);
factory.setPassword(password);
factory.setVirtualHost(virtualHost);
factory.setHost(hostName);
factory.setPort(portNumber);
ThreadPoolExecutor executor=xxx;//用于执行consumer的线程池
Connection conn = factory.newConnection(executor);
1.2.2 生产
Channel channel = conn.createChannel();
channel.queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments);
//durable-持久 ,exclusive-无连接自动删除队列 ,autoDelete-不使用自动删除队列
channel.confirmSelect() //设置确认机制
BasicProperties props = queueConfig.isDurable() ? MessageProperties.MINIMAL_PERSISTENT_BASIC: MessageProperties.MINIMAL_BASIC
channel.basicPublish(String exchange, String routingKey,BasicProperties props, byte[] body);//发送消息
channel.waitForConfirms(long timeout)// 等待队列发送确认
1.2.3 消费
Channel channel = conn.createChannel();
channel.queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments);
channel.basicQos() //流控
channel.basicConsume(queueName, autoAck, "myConsumerTag", //autoAck=false queue等consumer 发送确认才删除消息
new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag,
Envelope envelope,
AMQP.BasicProperties properties,
byte[] body)
throws IOException
{
String routingKey = envelope.getRoutingKey();
String contentType = properties.getContentType();
long deliveryTag = envelope.getDeliveryTag();
// (process the message components here ...)
channel.basicAck(deliveryTag, false); //void basicAck(long deliveryTag, boolean multiple) throws IOException; multiple表示是否一次确认多个
}
});
1.2.4注意事项
-
channel 可以缓存下来保证性能
-
消费者的并发度由 channel的数量和线程池中线程数较小的值决定
-
queueDeclare 可以重复调用,并保证队列存在
-
消息消费失败时,有如下处理策略
basicNack(deliveryTag, false, true)
队列中的消息会被派发到别的consumer,做出此响应的consumer仍然可以收到消息,直到所有消息完成
basicNack(deliveryTag, false, false)
做出此响应的consumer仍然可以收到消息,直到所有消息完成,但是失败的消息不会再出现在队列
无动作
消息处于没确定状态(对队列来讲),消息不会被派发给别的消费者(当该consumer被取消注册以后,消息被派发给别的consumer)
2 Redis
2.1 版本
Redis 一般使用Jedis客户端,引入依赖如下:
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
2.2 使用方式
使用Redis有多种方式,具体的操作方法可以见 https://redis.io/commands。
示例代码均可在
https://github.com/xetorthio/jedis/tree/master/src/test/java/redis/clients/jedis/tests
找到。
2.2.1 单节点
//初始化链接
HostAndPort hnp = xxx ;
Jedis jedis = new Jedis(hnp.getHost(), hnp.getPort(), 500);
jedis.connect();
jedis.auth("xxx");
//操作
String status = jedis.set("foo", "bar");
assertEquals("OK", status);
//关闭链接
jedis.disconnect();
-
new Jedis(hnp.getHost(), hnp.getPort(), 500)
用于初始化jedis对象,有多个重载方法,主要用于设定TCP的超时参数 -
jedis.connect()
用于显示建立TCP链接,可以不显示调用,内部发送命令时会保证建立TCP -
后面操作Redis都是使用
Jedis
对象 ,对Jedis
做了一层封装
如果要使用Pool管理连接,可以这样处理。
JedisPool pool = new JedisPool(new JedisPoolConfig(), hnp.getHost(), hnp.getPort(), 2000);
Jedis jedis = pool.getResource();
jedis.close();
pool.close();
2.2.2 一致性Hash
//初始化
List<JedisShardInfo> shards = new ArrayList<JedisShardInfo>(2);
JedisShardInfo shard1 = new JedisShardInfo(redis1.getHost(), redis1.getPort());
shard1.setPassword("foobared");
shards.add(shard1);
JedisShardInfo shard2 = new JedisShardInfo(redis2.getHost(), redis2.getPort());
shard2.setPassword("foobared");
shards.add(shard2);
ShardedJedis jedis = new ShardedJedis(shards);
//操作
String status = jedis.set("foo", "bar");
assertEquals("OK", status);
//关闭链接
jedis.close();
-
JedisShardInfo
用于指定分片的各种信息 -
ShardedJedis(List<JedisShardInfo> shards, Hashing algo)
可以指定Hash算法
如果要使用Pool管理连接,可以这样处理。
List<JedisShardInfo> shards = xxx;
ShardedJedisPool pool = new ShardedJedisPool(new GenericObjectPoolConfig(), shards);
ShardedJedis jedis = pool.getResource();
Jedis jedis = pool.getResource();
jedis.close();
pool.close();
2.2.3 Sentinel
// 初始化
HostAndPort sentinel1 = xxx;
HostAndPort sentinel2 = xxx;
Set<String> sentinels = new HashSet<String>();
sentinels.add(sentinel1.toString());
sentinels.add(sentinel2.toString());
JedisSentinelPool pool = new JedisSentinelPool(MASTER_NAME, sentinels,
new GenericObjectPoolConfig(), 2000, "foobared", 2);
// 获取Jedis
Jedis borrowed = pool.getResource();
borrowed.set("foo", "bar");
borrowed.close();
// 释放资源
pool.close();
3 Zookeeper
3.1 介绍
Zookeeper是一个分布式小文件系统,并且被设计为高可用性。通过选举算法和集群复制可以避免单点故障。通常Zookeeper由2n+1台servers组成,每个server都知道彼此的存在。每个server都维护内存状态镜像以及持久化存储的事务日志和快照。
3.1.1 数据模型
Zookeeper拥有一个层次的命名空间,这个和分布式的文件系统非常相似。不同的是ZooKeeper命名空间中的Znode,兼具文件和目录两种特点。既像文件一样维护着数据、元信息、ACL、时间戳等数据结构,又像目录一样可以作为路径标识的一部分,并可以具有子znode。用户对znode具有增、删、改、查等操作。
ZooKeeper目录树中每一个节点对应一个Znode。每个Znode维护着一个属性结构,它包含着版本号(dataVersion),时间戳(ctime,mtime)等状态信息。
3.1.2 角色
-
Leader,负责进行投票的发起和决议,更新系统状态
-
Learner,包括跟随者(follower)和观察者(observer),
-
Follower,用于接受客户端请求并向客户端返回结果,在选主过程中参与投票
-
Observer,可以接受客户端请求,将写请求转发给leader,但observer不参加投票过程,只同步leader的状态,observer的目的是为了扩展系统,提高读取速度。
-
Client,请求发起方
3.2 基础API
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.12.0</version>
</dependency>
所有示例代码均可以在
https://github.com/apache/curator/tree/master/curator-framework/src/test/java/org/apache/curator/framework/imps
找到。
3.2.1 创建会话
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3)
CuratorFramework client =
CuratorFrameworkFactory.builder()
.connectString(connectionInfo)
.sessionTimeoutMs(5000)
.connectionTimeoutMs(5000)
.retryPolicy(retryPolicy)
.build();
client.start();
3.2.2 创建节点
Zookeeper的节点创建模式
PERSISTENT:持久化
PERSISTENT_SEQUENTIAL:持久化并且带序列号
EPHEMERAL:临时
EPHEMERAL_SEQUENTIAL:临时并且带序列号
client.create().forPath("/my/path", myData)
client.create().creatingParentsIfNeeded().forPath("/my/path", myData);//递归创建
client.create().withMode(CreateMode.EPHEMERAL).forPath("path");
3.2.3 删除节点
client.delete().forPath("/my/path"); //只删除叶子节点
client.delete().deletingChildrenIfNeeded().forPath("path"); //递归删除其所有的子节点
3.2.4 获取子节点
client.getChildren().forPath("/")
3.2.5 读取数据
client.getData().forPath("path");//此方法返的返回值是byte[];
Stat stat = new Stat();
client.getData().storingStatIn(stat).forPath("path");//读取一个节点的数据内容,同时获取到该节点的stat
3.2.6 更新数据
client.setData().forPath("path","data".getBytes());
3.2.7 检查节点是否存在
client.checkExists().forPath("path");
3.2.8 异步执行
如果inBackground()
方法不指定executor,那么会默认使用Curator的EventThread去进行异步处理。
Executor executor = Executors.newFixedThreadPool(2);
client.create()
.creatingParentsIfNeeded()
.withMode(CreateMode.EPHEMERAL)
.inBackground((curatorFramework, curatorEvent) -> {
System.out.println(String.format("eventType:%s,resultCode:%s",curatorEvent.getType(),curatorEvent.getResultCode()));
},executor)
.forPath("path");
3.3 高级API
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>2.12.0</version>
</dependency>
所有示例代码均可以在
https://github.com/apache/curator/tree/master/curator-recipes/src/test/java/org/apache/curator/framework/recipes
找到。
3.3.1 监控
3.3.1.1 PathChildrenCache
当一个子节点增加, 更新,删除时, Path Cache会改变它的状态,
会包含最新的子节点,
子节点的数据和状态,而状态的更变将通过PathChildrenCacheListener通知。
Timing timing = new Timing();
CuratorFramework client = CuratorFrameworkFactory.newClient(server.getConnectString(), new RetryOneTime(1));
PathChildrenCache cache = new PathChildrenCache(client, "/a/b/test", true);
try
{
client.start();
final BlockingQueue<PathChildrenCacheEvent.Type> events = Queues.newLinkedBlockingQueue();
PathChildrenCacheListener listener = new PathChildrenCacheListener()
{
@Override
public void childEvent(CuratorFramework client, PathChildrenCacheEvent event) throws Exception
{
events.add(event.getType());
}
};
cache.getListenable().addListener(listener);
cache.start(PathChildrenCache.StartMode.POST_INITIALIZED_EVENT);
Assert.assertEquals(events.poll(timing.forWaiting().milliseconds(), TimeUnit.MILLISECONDS), PathChildrenCacheEvent.Type.CONNECTION_RECONNECTED);
Assert.assertEquals(events.poll(timing.forWaiting().milliseconds(), TimeUnit.MILLISECONDS), PathChildrenCacheEvent.Type.INITIALIZED);
client.create().forPath("/a/b/test/one");
client.create().forPath("/a/b/test/two");
Assert.assertEquals(events.poll(timing.forWaiting().milliseconds(), TimeUnit.MILLISECONDS), PathChildrenCacheEvent.Type.CHILD_ADDED);
Assert.assertEquals(events.poll(timing.forWaiting().milliseconds(), TimeUnit.MILLISECONDS), PathChildrenCacheEvent.Type.CHILD_ADDED);
client.delete().forPath("/a/b/test/one");
client.delete().forPath("/a/b/test/two");
client.delete().forPath("/a/b/test");
Assert.assertEquals(events.poll(timing.forWaiting().milliseconds(), TimeUnit.MILLISECONDS), PathChildrenCacheEvent.Type.CHILD_REMOVED);
Assert.assertEquals(events.poll(timing.forWaiting().milliseconds(), TimeUnit.MILLISECONDS), PathChildrenCacheEvent.Type.CHILD_REMOVED);
timing.sleepABit();
client.create().creatingParentContainersIfNeeded().forPath("/a/b/test/new");
Assert.assertEquals(events.poll(timing.forWaiting().milliseconds(), TimeUnit.MILLISECONDS), PathChildrenCacheEvent.Type.CHILD_ADDED);
}
finally
{
CloseableUtils.closeQuietly(cache);
CloseableUtils.closeQuietly(client);
}
3.3.1.2 NodeCache
Node Cache只是监听某一个特定的节点。
NodeCache cache = null;
Timing timing = new Timing();
CuratorFramework client = CuratorFrameworkFactory.newClient(server.getConnectString(), timing.session(), timing.connection(), new RetryOneTime(1));
client.start();
try
{
client.create().forPath("/test");
cache = new NodeCache(client, "/test/node");
cache.start(true);
final Semaphore semaphore = new Semaphore(0);
cache.getListenable().addListener
(
new NodeCacheListener()
{
@Override
public void nodeChanged() throws Exception
{
semaphore.release();
}
}
);
Assert.assertNull(cache.getCurrentData());
client.create().forPath("/test/node", "a".getBytes());
Assert.assertTrue(timing.acquireSemaphore(semaphore));
Assert.assertEquals(cache.getCurrentData().getData(), "a".getBytes());
client.setData().forPath("/test/node", "b".getBytes());
Assert.assertTrue(timing.acquireSemaphore(semaphore));
Assert.assertEquals(cache.getCurrentData().getData(), "b".getBytes());
client.delete().forPath("/test/node");
Assert.assertTrue(timing.acquireSemaphore(semaphore));
Assert.assertNull(cache.getCurrentData());
}
finally
{
CloseableUtils.closeQuietly(cache);
TestCleanState.closeAndTestClean(client);
}
3.3.1.3 TreeCache
Tree Cache可以监控整个树上的所有节点,类似于PathCache和NodeCache的组合。
3.3.2 Leader Election
3.3.2.1 LeaderLatch
构造函数如下:
public LeaderLatch(CuratorFramework client, String latchPath)
public LeaderLatch(CuratorFramework client, String latchPath, String id)
主要方法:
start()
:启动选举
hasLeadership()
:判断当前是否是Leader
await()
:直到成为Leader才返回
close()
:释放LeaderShip
异常处理:
LeaderLatch实例可以增加ConnectionStateListener来监听网络连接问题。 当 SUSPENDED
或 LOST 时,
leader不再认为自己还是leader。当LOST后连接重连后RECONNECTED,LeaderLatch会删除先前的ZNode然后重新创建一个。
示例代码见:
TestLeaderLatch
3.3.2.2 LeaderSelector
构造函数如下:
public LeaderSelector(CuratorFramework client, String mutexPath,LeaderSelectorListener listener)
public LeaderSelector(CuratorFramework client, String mutexPath, ThreadFactory threadFactory, Executor executor, LeaderSelectorListener listener)
主要方法:
start()
:启动选举
takeLeadership()
:当实例取得领导权时被调用,返回时释放领导权
autoRequeue()
:方法的调用确保此实例在释放领导权后还可能获得领导权。
close()
:释放LeaderShip
异常处理:
LeaderSelectorListener类继承ConnectionStateListener.LeaderSelector必须小心连接状态的
改变. 如果实例成为leader, 它应该相应SUSPENDED 或 LOST. 当 SUSPENDED 状态出现时,
实例必须假定在重新连接成功之前它可能不再是leader了。 如果LOST状态出现,
实例不再是leader, takeLeadership方法返回。
示例代码见:
TestLeaderSelector
3.3.3 Barrier
DistributedBarrier类实现了栅栏的功能。 它的构造函数如下:
public DistributedBarrier(CuratorFramework client, String barrierPath)
client - client
barrierPath - path to use as the barrier
主要方法:
setBarrier()
:设置栅栏,它将阻塞在它上面等待的线程
waitOnBarrier()
:等待放行条件
removeBarrier()
:移除栅栏,所有等待的线程将继续执行
异常处理:
DistributedBarrier 会监控连接状态,当连接断掉时waitOnBarrier()方法会抛出异常。
示例代码见:
TestDistributedBarrier
Double Barrier
允许客户端在计算的开始和结束时同步。当足够的进程加入到双栅栏时,进程开始计算,
当计算完成时,离开栅栏。
示例代码见:
TestDistributedDoubleBarrier
3.3.4 计数器
DistributedAtomicLong 尝试使用乐观锁的方式设置计数器。
主要方法:
get()
: 获取当前值
increment()
: 加一
decrement()
: 减一
add()
: 增加特定的值
subtract()
: 减去特定的值
trySet()
: 尝试设置计数值
forceSet()
: 强制设置计数值
你必须检查返回结果的succeeded(), 它代表此操作是否成功。 如果操作成功,
preValue()代表操作前的值, postValue()代表操作后的值。
示例代码见:
TestDistributedAtomicLong
3.3.5 锁
3.3.5.1 可重入锁
构造函数:
public InterProcessMutex(CuratorFramework client, String path)
主要方法: acquire()
:获取锁 release()
:释放锁 不应该在
异常处理:
使用 ConnectionStateListener 处理连接状态的改变。 当连接LOST时你不再拥有锁。
3.3.5.2 不可重入锁
构造函数:
public InterProcessSemaphoreMutex(CuratorFramework client, String path)