rrpc:使用客户端+轮询实现负载均衡

一、什么是负载均衡

Load balancing,即负载均衡,是一种计算机技术,用来在多个计算机(计算机集群)、网络连接、CPU、磁盘驱动器或其他资源中分配负载,以达到最优化资源使用、最大化吞吐率、最小化响应时间、同时避免过载的目的。在我们的rrpc中使用负载均衡将请求负载分配到不同的节点上。

OIS七层模型

负载均衡可以根据不同的标准进行分类。以下是一些常见的负载均衡分类:

负载均衡服务器在决定将请求转发到具体哪台真实服务器的时候,是通过负载均衡算法来实现的。负载均衡算法,是一个负载均衡服务器的核心。

就像电影院门口的引导员一样,他根据什么把排队人员分配到具体的入口呢?是哪个入口人少吗?还是哪个入口速度最快?还是哪个入口最近呢?

 

 二、rrpc框架中的负载均衡

我们使用客户端负载均衡,让客户端直接选择合适的节点,使用轮询、加权轮询或一致性哈希等算法,实现负载均衡

注:使用服务端负载均衡,需要配置额外的硬件负载均衡器或云负载均衡服务会带来额外的成本,还会带来额外的网络跳转,另外负载均衡可能成为性能瓶颈,减少了灵活性和可定制性,因此不使用

三、客户端+轮询负载均衡方式

在客户端访问服务端时,一个客户端可能对应多个服务端,使用轮询负载均衡的方式,让客户端按顺序将请求分配给服务端

1、完成效果

启动三个服务端 

 客户端发送3次请求,将请求按顺序分配给客户端

 2、代码实现

 实现负载均衡的大体思路      

使用模板方法设计模式,模板方法模式(Template Method Pattern)在一个抽象类公开定义了执行它的模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行;

    1.在我们的rrpc中,定义了一个loadBalancer作为负载均衡的接口,定义了抽象类AbstractLoadBalancer implements loadBalancer,

    2.在 AbstractLoadBalancer 中定义了getSelector作为模板方法,用于负载均衡的选择器

    3.定义RoundRobinLoadBalancer继承AbstractLoadBalancer,重写getSelector方法,并在类中定义选择器RoundRobinSelector方法实现轮询负载均衡算法

     4.创建方法reLoadBalance(),当服务上下线时进行重新的负载均衡

1.Selector接口:用于服务节点的选择

public interface Selector {
    /**
     *
     * 根据服务列表执行一种算法 获取一个服务节点
     * 获取可用节点
     * @param
     * @return  具体的服务节点
     */
    InetSocketAddress getNext();

2.LoadBalancer接口

/**\
 * 负载均衡器的接口
 * 根据服务拉取列表 --> 对列表做缓存 --> 找到一个可用的服务
 * 根据服务列表找到一个可用的服务
 */
public interface LoadBalancer {
    /**
     * 根据服务名找到一个可用的服务
     * @param serviceName 服务名称
     * @return 服务地址
     */
    InetSocketAddress selectServiceAddress(String serviceName);

    /**
     * 当感知节点发生了动态上下线,我们需要进行重新的负载均衡
     * @param serviceName 服务的名称
     */
    void reLoadBalance(String serviceName, List<InetSocketAddress> addresses);
}

 3.AbstractLoadBalancer抽象模板类

public abstract class AbstractLoadBalancer implements LoadBalancer{

    //一个服务匹配一个selector
    private Map<String,Selector> cache = new ConcurrentHashMap<>(8);

    @Override
    public InetSocketAddress selectServiceAddress(String serviceName) {
        //第一部优先从cache中获取一个选择器
        Selector selector = cache.get(serviceName);
        //第二部如果没有缓存,就需要为service创建一个selector
        if (selector == null){
            //对于负载均衡器,内部维护服务列表作为缓存
            List<InetSocketAddress> serviceList = RrpcBootstrap.getInstance()
                    .getRegistry().lookup(serviceName);

            //提供一些算法负责选择合适的节点
            selector = getSelector(serviceList);

            cache.put(serviceName,selector);
        }


        //获取可用节点
        return selector.getNext();
    }

    /**
     * 当感知节点发生了动态上下线,我们需要进行重新的负载均衡
     * @param serviceName 服务的名称
     */
    @Override
    public synchronized void reLoadBalance(String serviceName,List<InetSocketAddress> addresses) {
        //根据新的服务列表生成新的selector
        cache.put(serviceName,getSelector(addresses));
    }

    /**
     * 模板设计模式
     * 由子类进行扩展
     * @param serviceList
     * @return 负载均衡选择器
     */
    protected abstract Selector getSelector(List<InetSocketAddress> serviceList) ;
}

4. RoundRobinLoadBalancer

@Slf4j
public class RoundRobinLoadBalancer extends AbstractLoadBalancer {


    @Override
    protected Selector getSelector(List<InetSocketAddress> serviceList) {
        return new RoundRobinSelector(serviceList);
    }

    /**
     * 选择器
     * 将服务列表缓存进去
     */
    private static class RoundRobinSelector implements Selector{

        private List<InetSocketAddress> serviceList;
        //需要一个原子性的游标,避免因高并发造成的线程安全问题,进行服务列表的选择
        //众所周知,AtomicInteger用于多线程下线程安全的数据读写操作,
        //避免使用锁同步,底层采用CAS实现,内部的存储值使用volatile修饰,因此多线程之间是修改可见的。
        private AtomicInteger index;

        public RoundRobinSelector(List<InetSocketAddress> serviceList) {
            this.serviceList = serviceList;
            this.index = new AtomicInteger(0);
        }

        @Override
        public InetSocketAddress getNext() {
            if (serviceList == null || serviceList.size() == 0){
                log.error("进行负载均衡选取节点时,发现服务列表为空");
                throw new LoadBalanceException();
            }

            InetSocketAddress address = serviceList.get(index.get());
            //如果游标到了最后一位 返回index=0
            if (index.get() == serviceList.size() - 1){
                index.set(0);
            }else {
                //游标后移1位
                index.incrementAndGet();
            }


            return address;
        }

        @Override
        public void reBalance() {

        }
    }
}

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
要在Python中实现接入阿里云物联网平台的发布订阅功能,你需要进行以下几个步骤: 1. 创建阿里云账号并开通物联网平台服务。 2. 使用阿里云物联网平台的SDK,安装aliyun-python-sdk-iot模块。 3. 获取阿里云账号的AccessKey ID和AccessKey Secret,并创建IoT客户端。 4. 创建Topic和订阅。 下面是Python代码示例: ```python from aliyunsdkcore.client import AcsClient from aliyunsdkcore.auth.credentials import AccessKeyCredential from aliyunsdkiot.request.v20180120 import * # 阿里云账号的AccessKey ID和AccessKey Secret access_key_id = 'your_access_key_id' access_key_secret = 'your_access_key_secret' # 创建IoT客户端 cred = AccessKeyCredential(access_key_id, access_key_secret) client = AcsClient("", "", "cn-shanghai", credential=cred) # 创建Topic和订阅 product_key = 'your_product_key' topic_short_name = 'test_topic' request = CreateTopicRequest.CreateTopicRequest() request.set_ProductKey(product_key) request.set_TopicShortName(topic_short_name) client.do_action_with_exception(request) request = CreateSubscribeRelationRequest.CreateSubscribeRelationRequest() request.set_ProductKey(product_key) request.set_TopicShortName(topic_short_name) request.set_DeviceName('your_device_name') client.do_action_with_exception(request) # 发布消息 request = PubRequest.PubRequest() request.set_ProductKey(product_key) request.set_TopicFullName('/{}/{}/{}'.format(product_key, topic_short_name, 'user')) request.set_MessageContent('Hello, World!') client.do_action_with_exception(request) # 接收消息 request = RRpcRequest.RRpcRequest() request.set_ProductKey(product_key) request.set_DeviceName('your_device_name') request.set_Topic('/{}/{}/{}'.format(product_key, topic_short_name, 'user/get')) request.set_MessageContent('Hello, World!') response = client.do_action_with_exception(request) print(response) ``` 其中,access_key_id和access_key_secret需要替换为你自己的阿里云账号的AccessKey ID和AccessKey Secret,product_key和topic_short_name可以根据自己的需求自定义,device_name需要替换为你自己的设备名称。在创建订阅时,需要将订阅关系建立在设备和Topic之间,这样设备才能接收到消息。在发布消息时,需要指定消息的Topic全名。在接收消息时,需要指定设备名称和Topic名称,然后发送一个RRpc请求,等待设备返回响应。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

北方569

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值