中间件使用指引

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)

猜你喜欢

转载自blog.csdn.net/qq_31281327/article/details/115234064