精华内容
下载资源
问答
  • 腾讯微云网盘直链解析源码(PHP)
    千次阅读
    2021-04-08 10:31:39

    19c86f2096c7978e45f1fcb2798082d1.png

    腾讯微云网盘文件直链解析,需要cookie。

    没开会员,下载速度在100K每秒左右,

    开了会员,基本满速宽带下载。

    (测试呢,就算你开了会员,加速下载对于海外用户下载,效果不佳)

    e7e21459e975681905f3bbdf3d69f619.png

    5dc75220bf55a9885a63b29e3d5b91f4.png

    非分享链接解析,因此无流量上限。

    JSON 测试演示:

    (cookie 均已内置)

    (非微云VIP)

    文件:https://www.h2sheji.com/doc/api/jx/weiyun/weiyun_demo.php?vid=6ad1c921-a72a-48c1-bdd2-6260a2fa6d16&dir=104b79a051e8a9462ac75b3c1c352d11https://www.h2sheji.com/doc/api/jx/weiyun/weiyun_demo.php?vid=6ad1c921-a72a-48c1-bdd2-6260a2fa6d16&dir=104b79a051e8a9462ac75b3c1c352d11

    (微云VIP,不限速,此处仅用于测试若出现过期限速,请自行提供vip COOKIE 并联系管理员测试,或者自行使用下方开放api测试)

    开放API测试:

    1,获取加密cookie :

    https://www.h2sheji.com/doc/api/jx/weiyun/weiyun.php?type=getck&h2ck=[QQcookie]

    (cookie 关键参数 uin skey   例:  https://www.h2sheji.com/doc/api/jx/weiyun/weiyun.php?type=getck&h2ck=uin=o1000010000;skey=@qqcijh2sheji;)

    2,解析文件api

    https://www.h2sheji.com/doc/api/jx/weiyun/weiyun.php?vid=[文件ID]&dir=[目录ID]&h2ck=[上方获取的QQ cookie 加密密文]

    解析源码

    (目录ID,如果你解析的所有文件都在同一个文件夹中,此目录ID可固定)

    (一次性付费,无维护,不包更新,本源码仅限学习交流使用,请勿用于任何非法活动,否则后果自负!!!)

    更多相关内容
  • RocketMQ源码系列() NameServer 核心源码解析

    万次阅读 热门讨论 2021-06-15 17:52:51
    、NameServer 介绍 二、NameServer 功能列表 三、NameServer 架构分析 四、NameServer 架构

    目录

    一、NameServer 介绍

    二、NameServer 功能列表

    三、NameServer 架构分析

    四、NameServer 工程目录解析

    五、NameServer 启动流程分析

    1)  创建NameSrvController

    2)  执行initialize()加载需要的配置

    3)  启动server

    六、NameServer核心源码解析

    1. 路由注册

    1)  broker向NameServer 发送心跳包

     2)  NameServer 处理心跳包

    2. 路由删除

    3. 路由发现

    小结


    rocketmq版本: 4.8.0

    一、NameServer 介绍

            NameServer 是rocketmq核心组件之一,与zookeeper一样天生具有分布式的特性,在rocketmq中担当着路由注册、发现、动态地维护broker相关信息的角色, NameServer 不提供Master-slave同步机制,但是能够保证数据的最终一致性。

    二、NameServer 功能列表

    1.  动态路由发现和注册功能。broker 启动时,会将brokerAddr 注册到NameServer里, 路由发现是指客户端会定时的向NameServer根据topic拉取路由的最新信息。
    2.  动态剔除功能。每隔10 s NameServer 会自动扫描所有的broker, 如果有broker失效,那么会从地址列表里将其剔除掉。

    三、NameServer 架构分析

    下面是 rocketmq 的部署图

    核心原理解析                                

             Broker消息服务器启动时会自动向NameServer 注册信息,消息生产者在发送消息时,会在NameServer的地址列表里通过负载均衡选择一个Broker进行消息发送。 NameServer 与每台broker保持长连接,broker会每隔30s向NameServer发送一个心跳包,NameServer每间隔10s查看broker是否存活,如果broker挂掉了,判断挂掉的逻辑是brokerLiveTable检测上次的心跳包与当前系统时间的时间差,如果时间戳大于120s, 那么就将broker从服务地址列表里剔除。

            这样设计的目的是降低NameServer 的复杂性, 在消息发送端提供容错机制来保证消息发送的高可用性。

            NameServer  可以通过集群来保证高可用性,但在同一时刻有可能获取到数据是不一致的,因为不提供同步机制,但能够保证多个节点的最终一致性。NameServer 这样设计是为了简单高效。

    四、NameServer 工程目录解析

    工程目录结构以及解析如下: 

    namesrv
    ├─ NamesrvController.java // 执行初始化逻辑,加载配置、注册Processor等
    ├─ NamesrvStartup.java // NameServer的启动类, 启动netty server
    ├─ kvconfig
    │    ├─ KVConfigManager.java // namespace和config配置管理
    │    └─ KVConfigSerializeWrapper.java // 将获取到的配置json序列化
    ├─ processor
    │    ├─ ClusterTestRequestProcessor.java //处理请求类型。
    │    └─ DefaultRequestProcessor.java  // 默认地请求处理器, 处理数据包
    └─ routeinfo
           ├─ BrokerHousekeepingService.java // netty 的channel共用方法抽象
           └─ RouteInfoManager.java   // 路由管理器,维护topic, broker, 
    //clusterName, brokerAddr等信息

            分析发现netty 是rocketmq 网络通信的核心,掌握netty 的常见用法是非常有必要的。

    五、NameServer 启动流程分析

    1)  创建NameSrvController

            加载 namesrvConfig 和 nettyServerConfig, 如果有手动配置也可以生效, 使用option类封装参数,在程序运行前添加配置Program arguments, 添加的格式: 例如 -c , -p 等。

        public static NamesrvController createNamesrvController(String[] args) throws IOException, JoranException {
       ....
        final NamesrvController controller = new NamesrvController(namesrvConfig, nettyServerConfig);
    
            // remember all configs to prevent discard
            controller.getConfiguration().registerConfig(properties);
    
            return controller;
    }

    NameSrv的配置存放在 user.home\namesrv\ 目录下:

    2) 执行initialize()加载需要的配置

            NamesrvController 在执行start()方法前需要做一些准备工作,比如加载配置、创建Netty Server实例、注册请求处理器、扫描所有的失联的broker等

        具体的解释如下注释:

        public boolean initialize() {
           // 加载k,v 相关配置,含自定义配置。
            this.kvConfigManager.load();
            // 启动netty server, 管理channel
            this.remotingServer = new NettyRemotingServer(this.nettyServerConfig, this.brokerHousekeepingService);
            //  初始化netty 线程池
            this.remotingExecutor =
                Executors.newFixedThreadPool(nettyServerConfig.getServerWorkerThreads(), new ThreadFactoryImpl("RemotingExecutorThread_"));
            //  注册netty 请求Handler, 可以通过NettyRequestProcessor接口找到其实现类
            this.registerProcessor();
            // 与broker建立长连接,扫描所有的broker
            this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
    
                @Override
                public void run() {
                    NamesrvController.this.routeInfoManager.scanNotActiveBroker();
                }
            }, 5, 10, TimeUnit.SECONDS);
           // 打印所有的config
            this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
    
                @Override
                public void run() {
                    NamesrvController.this.kvConfigManager.printAllPeriodically();
                }
            }, 1, 10, TimeUnit.MINUTES);
             // 监听文件里的配置是否修改
            if (TlsSystemConfig.tlsMode != TlsMode.DISABLED) {
                // Register a listener to reload SslContext
                try {
                    fileWatchService = new FileWatchService(
                        new String[] {
                            TlsSystemConfig.tlsServerCertPath,
                            TlsSystemConfig.tlsServerKeyPath,
                            TlsSystemConfig.tlsServerTrustCertPath
                        },
                        new FileWatchService.Listener() {
                            boolean certChanged, keyChanged = false;
                            @Override
                            public void onChanged(String path) {
                                if (path.equals(TlsSystemConfig.tlsServerTrustCertPath)) {
                                    log.info("The trust certificate changed, reload the ssl context");
                                    reloadServerSslContext();
                                }
                                if (path.equals(TlsSystemConfig.tlsServerCertPath)) {
                                    certChanged = true;
                                }
                                if (path.equals(TlsSystemConfig.tlsServerKeyPath)) {
                                    keyChanged = true;
                                }
                                if (certChanged && keyChanged) {
                                    log.info("The certificate and private key changed, reload the ssl context");
                                    certChanged = keyChanged = false;
                                    reloadServerSslContext();
                                }
                            }
                            private void reloadServerSslContext() {
                                ((NettyRemotingServer) remotingServer).loadSslContext();
                            }
                        });
                } catch (Exception e) {
                    log.warn("FileWatchService created error, can't load the certificate dynamically");
                }
            }
    
            return true;
        }

           如果initialize()方法返回false, 那么需要检查一些相关配置是否正确, 返回true后,就可以执行最后一步controller.start()方法, 该方法表示NameServer正式启动。

    3) 启动server

          接下来看下源代码分析start()方法做了哪些事

      public void start() throws Exception {
         // 1. 启动netty server
            this.remotingServer.start();
          // 2. 启动文件扫描线程,监听核心配置是否修改。
            if (this.fileWatchService != null) {
                this.fileWatchService.start();
            }
        }
    

            可以通过debug发现,首先会进入到NettyRemotingServer类里的start()方法, 该方法实现了nettyServer, 初始化netty的线程组和实例化 ServerBootStrap。

     然后开启一个线程执行FileWatchService 的run()方法:

    通过此线程扫描配置文件是否被修改。

    NameServer启动成功后,会在控制台打印 boot success的字样。

    六、NameServer核心源码解析

    1. 路由注册

    1)  broker向NameServer 发送心跳包

            找到brokerController的start()方法里,broker 通过 BrokerController.this.registerBrokerAll(true,false) 方法来向NameServer 发送心跳包,其中使用定时任务 sheduledExecutorService 线程池定时发送,每隔30s 发送一次, brokerConfig.getRegisterNameServerPeriod() 的默认值为30s。

        this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
    
                @Override
                public void run() {
                    try {
                        BrokerController.this.registerBrokerAll(true, false, brokerConfig.isForceRegister());
                    } catch (Throwable e) {
                        log.error("registerBrokerAll Exception", e);
                    }
                }
            }, 1000 * 10, Math.max(10000, Math.min(brokerConfig.getRegisterNameServerPeriod(), 60000)), TimeUnit.MILLISECONDS);
    

     然后进入到doRegisterBrokerAll()方法,找到BrokerOuterApi里的registerBrokerAll()方法, 用RegiterBrokerRequestHeader类封装broker相关的信息, RegiterBrokerRequestHeader 主要属性如下:

    • brokerName:  broker名称。
    • brokerAddr:  broker的地址。
    • cluterName: broker所在集群的名称。
    • haServerAddr: 集群master的地址。
    • brokerId:   brokerId为0的时候表示该broker为master, 如果大于0,表示该broker为slave。

    brokerId=0为master节点的配置在MixALL配置中:

    然后会调用到registerBrokerAll() 方法, 最终会将该broker信息注册到所有的NameServer上。

      public List<RegisterBrokerResult> registerBrokerAll(
            final String clusterName,
            final String brokerAddr,
            final String brokerName,
            final long brokerId,
            final String haServerAddr,
            final TopicConfigSerializeWrapper topicConfigWrapper,
            final List<String> filterServerList,
            final boolean oneway,
            final int timeoutMills,
            final boolean compressed) {
     
            final List<RegisterBrokerResult> registerBrokerResultList = new CopyOnWriteArrayList<>();
            List<String> nameServerAddressList = this.remotingClient.getNameServerAddressList();
            if (nameServerAddressList != null && nameServerAddressList.size() > 0) {
             // 封装broker信息
                final RegisterBrokerRequestHeader requestHeader = new RegisterBrokerRequestHeader();
                requestHeader.setBrokerAddr(brokerAddr);
                requestHeader.setBrokerId(brokerId);
                requestHeader.setBrokerName(brokerName);
                requestHeader.setClusterName(clusterName);
                requestHeader.setHaServerAddr(haServerAddr);
                requestHeader.setCompressed(compressed);
    
                RegisterBrokerBody requestBody = new RegisterBrokerBody();
                requestBody.setTopicConfigSerializeWrapper(topicConfigWrapper);
                requestBody.setFilterServerList(filterServerList);
                final byte[] body = requestBody.encode(compressed);
                final int bodyCrc32 = UtilAll.crc32(body);
                requestHeader.setBodyCrc32(bodyCrc32);
        // 等待所有的NameServer都含有broker信息后,才表示执行完毕。
                final CountDownLatch countDownLatch = new CountDownLatch(nameServerAddressList.size());
           // 把该broker的信息注册到所有的NameServer上。
                for (final String namesrvAddr : nameServerAddressList) {
                    brokerOuterExecutor.execute(new Runnable() {
                        @Override
                        public void run() {
                            try {
                                RegisterBrokerResult result = registerBroker(namesrvAddr,oneway, timeoutMills,requestHeader,body);
                                if (result != null) {
                                    registerBrokerResultList.add(result);
                                }
    
                                log.info("register broker[{}]to name server {} OK", brokerId, namesrvAddr);
                            } catch (Exception e) {
                                log.warn("registerBroker Exception, {}", namesrvAddr, e);
                            } finally {
                                countDownLatch.countDown();
                            }
                        }
                    });
                }
    
                try { 
               // 默认超时时间为6s, 在BrokerConfig里配有registerBrokerTimeoutMills=6000
                    countDownLatch.await(timeoutMills, TimeUnit.MILLISECONDS);
                } catch (InterruptedException e) {
                }
            }
    
            return registerBrokerResultList;
        }

            而broker相关信息是通过netty 发送给NameServer, broker信息的请求注册方式有oneway 和同步和异步,默认发送注册请求的方式是同步的。

            可以在BrokerOuterAPI类里的registerBroker(final String namesrvAddr, final boolean oneway, final int timeoutMills, final RegisterBrokerRequestHeader requestHeader, final byte[] body) 里找到通过同步的方式发送注册请求,同步的注册方式如下:

     2)  NameServer 处理心跳包

             NameServer接收到broker发送过来的请求后,首先会在DefaultRequestProcessor 网络处理器解析请求类型,请求类型如果为RequestCode.REGISTER_BROKER, 则最终的请求会到RouteInfoManager里的registerBroker()方法。

     public RemotingCommand registerBroker(ChannelHandlerContext ctx,
                                              RemotingCommand request) throws RemotingCommandException {
            final RemotingCommand response = RemotingCommand.createResponseCommand(RegisterBrokerResponseHeader.class);
            final RegisterBrokerResponseHeader responseHeader = (RegisterBrokerResponseHeader) response.readCustomHeader();
            final RegisterBrokerRequestHeader requestHeader =
                    (RegisterBrokerRequestHeader) request.decodeCommandCustomHeader(RegisterBrokerRequestHeader.class);
    
            if (!checksum(ctx, request, requestHeader)) {
                response.setCode(ResponseCode.SYSTEM_ERROR);
                response.setRemark("crc32 not match");
                return response;
            }
            // 解析数据包
            TopicConfigSerializeWrapper topicConfigWrapper;
            if (request.getBody() != null) {
                topicConfigWrapper = TopicConfigSerializeWrapper.decode(request.getBody(), TopicConfigSerializeWrapper.class);
            } else {
                topicConfigWrapper = new TopicConfigSerializeWrapper();
                topicConfigWrapper.getDataVersion().setCounter(new AtomicLong(0));
                topicConfigWrapper.getDataVersion().setTimestamp(0);
            }
            // 用RouteInfoManager 注册broker
            RegisterBrokerResult result = this.namesrvController.getRouteInfoManager().registerBroker(
                    requestHeader.getClusterName(),
                    requestHeader.getBrokerAddr(),
                    requestHeader.getBrokerName(),
                    requestHeader.getBrokerId(),
                    requestHeader.getHaServerAddr(),
                    topicConfigWrapper,
                    null,
                    ctx.channel()
            );
            // 响应broker
            responseHeader.setHaServerAddr(result.getHaServerAddr());
            responseHeader.setMasterAddr(result.getMasterAddr());
    
            byte[] jsonValue = this.namesrvController.getKvConfigManager().getKVListByNamespace(NamesrvUtil.NAMESPACE_ORDER_TOPIC_CONFIG);
            response.setBody(jsonValue);
            response.setCode(ResponseCode.SUCCESS);
            response.setRemark(null);
            return response;
        }
    

            RouteInfoManager 里的registerBroker方法将broker的信息最终添加到 clusterAddrTable、brokerAddrTable、brokerLiveTable、filterServerTable里。

            画了一下broker的在NameSrv中的注册流程图

    2. 路由删除

            RouteInfoManager 的scanNotActiveBroker ()方法, 采用了单线程定时线程池每隔10s扫描所有broker的策略, 该方法在NamesrvController里的initialize()方法里, newSingleThreadScheduledExecutor线程池里只有一个线程实例,利用此线程池能极大地减少系统资源地开销,因为扫描broker本身不需要过多的资源,开启一个线程足以。

        this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
    
                @Override
                public void run() {
                    NamesrvController.this.routeInfoManager.scanNotActiveBroker();
                }
            }, 5, 10, TimeUnit.SECONDS);

            NameServer是如何判定broker失效的呢?

            继续跟着源码进入到scanNotActiveBroker()方法, 判定失效的逻辑是: 如果当前时间戳- 上一次更新的时间戳> 120s。那么判断该broker是失效的。 BROKER_CHANNEL_EXPIRED_TIME默认值为120s。因为broker每隔30s会给NameServer发送一次心跳信息,因此此方式可以判定broker是否失效。

       public void scanNotActiveBroker() {
            Iterator<Entry<String, BrokerLiveInfo>> it = this.brokerLiveTable.entrySet().iterator();
            while (it.hasNext()) {
                Entry<String, BrokerLiveInfo> next = it.next();
                long last = next.getValue().getLastUpdateTimestamp();
                if ((last + BROKER_CHANNEL_EXPIRED_TIME) < System.currentTimeMillis()) {
                    RemotingUtil.closeChannel(next.getValue().getChannel());
                    it.remove();
                    log.warn("The broker channel expired, {} {}ms", next.getKey(), BROKER_CHANNEL_EXPIRED_TIME);
                    this.onChannelDestroy(next.getKey(), next.getValue().getChannel());
                }
            }
        }

            接着会将broker相关的信息从brokerLiveTable中移除掉,同时销毁掉netty对应的channel。brokerLiveTable是一个hashmap,归RouteInfoManager类持有。

    3. 路由发现

            RocketMQ的路由发现是非实时的,当Topic路由发生变化时,NameServer不主动推送给客户端,而是由客户端定时拉取主题最新的路由。根据主题拉取最新路由的编码为: GET_ROUTEINFO_BY_TOPIC。

            我们可以找到DefaultRequestProcessor处理器里的processRequest()方法,该方法用来处理Netty请求, 该方法有个判断,当request里的code为GET_ROUTEINFO_BY_TOPIC时,执行this.getRouteInfoByTopic(ctx, request)方法。

       @Override
        public RemotingCommand processRequest(ChannelHandlerContext ctx,
                                              RemotingCommand request) throws RemotingCommandException {
    
            if (ctx != null) {
                log.debug("receive request, {} {} {}",
                        request.getCode(),
                        RemotingHelper.parseChannelRemoteAddr(ctx.channel()),
                        request);
            }
           // 根据请求代码code来判断业务逻辑
    
            switch (request.getCode()) {
                case RequestCode.PUT_KV_CONFIG:
                    return this.putKVConfig(ctx, request);
                case RequestCode.GET_KV_CONFIG:
                    return this.getKVConfig(ctx, request);
                case RequestCode.DELETE_KV_CONFIG:
                    return this.deleteKVConfig(ctx, request);
                case RequestCode.QUERY_DATA_VERSION:
                    return queryBrokerTopicConfig(ctx, request);
                // 注册broker
                case RequestCode.REGISTER_BROKER:
                    Version brokerVersion = MQVersion.value2Version(request.getVersion());
                    if (brokerVersion.ordinal() >= MQVersion.Version.V3_0_11.ordinal()) {
                        return this.registerBrokerWithFilterServer(ctx, request);
                    } else {
                        return this.registerBroker(ctx, request);
                    }
                    // 移除broker
                case RequestCode.UNREGISTER_BROKER:
                    return this.unregisterBroker(ctx, request);
                // 根据topic获取路由
                case RequestCode.GET_ROUTEINFO_BY_TOPIC:
                    return this.getRouteInfoByTopic(ctx, request);
                case RequestCode.GET_BROKER_CLUSTER_INFO:
                    return this.getBrokerClusterInfo(ctx, request);
                case RequestCode.WIPE_WRITE_PERM_OF_BROKER:
                    return this.wipeWritePermOfBroker(ctx, request);
                case RequestCode.GET_ALL_TOPIC_LIST_FROM_NAMESERVER:
                    return getAllTopicListFromNameserver(ctx, request);
                case RequestCode.DELETE_TOPIC_IN_NAMESRV:
                    return deleteTopicInNamesrv(ctx, request);
                case RequestCode.GET_KVLIST_BY_NAMESPACE:
                    return this.getKVListByNamespace(ctx, request);
                case RequestCode.GET_TOPICS_BY_CLUSTER:
                    return this.getTopicsByCluster(ctx, request);
                case RequestCode.GET_SYSTEM_TOPIC_LIST_FROM_NS:
                    return this.getSystemTopicListFromNs(ctx, request);
                case RequestCode.GET_UNIT_TOPIC_LIST:
                    return this.getUnitTopicList(ctx, request);
                case RequestCode.GET_HAS_UNIT_SUB_TOPIC_LIST:
                    return this.getHasUnitSubTopicList(ctx, request);
                case RequestCode.GET_HAS_UNIT_SUB_UNUNIT_TOPIC_LIST:
                    return this.getHasUnitSubUnUnitTopicList(ctx, request);
                case RequestCode.UPDATE_NAMESRV_CONFIG:
                    return this.updateConfig(ctx, request);
                case RequestCode.GET_NAMESRV_CONFIG:
                    return this.getConfig(ctx, request);
                default:
                    break;
            }
            return null;
        }

     然后DefaultRequestProceesor类里的getRouteInfoByTopic(ctx,request)方法里主要做了两件事:

    1) 根据topic从RouteInfoManager的topicQueueTable里获取到所有的QueueData和BrokerData, 然后将他们设置到topicRouteData里返回出去。

    2) 判断指定的topic是否是顺序消息的topic,如果是那么给返回配置顺序消息的路由, 即给setOrderTopicConf赋值。

      public RemotingCommand getRouteInfoByTopic(ChannelHandlerContext ctx,
                                                   RemotingCommand request) throws RemotingCommandException {
            final RemotingCommand response = RemotingCommand.createResponseCommand(null);
            final GetRouteInfoRequestHeader requestHeader =
                    (GetRouteInfoRequestHeader) request.decodeCommandCustomHeader(GetRouteInfoRequestHeader.class);
            // 1. 获取到topicRouteData,包含topic所有的QueueData和BrokerData
            TopicRouteData topicRouteData = this.namesrvController.getRouteInfoManager().pickupTopicRouteData(requestHeader.getTopic());
    
            if (topicRouteData != null) {
                if (this.namesrvController.getNamesrvConfig().isOrderMessageEnable()) {
                    // 2. 判断是否是顺序消息的topic, 如果是顺序消息,那么给该请求返回顺序消息的路由
                    String orderTopicConf =
                            this.namesrvController.getKvConfigManager().getKVConfig(NamesrvUtil.NAMESPACE_ORDER_TOPIC_CONFIG,
                                    requestHeader.getTopic());
                    topicRouteData.setOrderTopicConf(orderTopicConf);
                }
    
                byte[] content = topicRouteData.encode();
                response.setBody(content);
                response.setCode(ResponseCode.SUCCESS);
                response.setRemark(null);
                return response;
            }
    
            response.setCode(ResponseCode.TOPIC_NOT_EXIST);
            response.setRemark("No topic route info in name server for the topic: " + requestHeader.getTopic()
                    + FAQUrl.suggestTodo(FAQUrl.APPLY_TOPIC_URL));
            return response;
        }

    小结

    1. NameServer相当于rocketmq的注册中心,能够维护并实时监听broker的地址信息和队列信息等。

    2. NameServer和broker之间是基于netty通信的。

    3. DefaultRequestProcessor的 getRouteInfoByTopic(ChannelHandlerContext ctx,RemotingCommand request)方法是根据topc获取到路由信息(包含topic对应的所有queue和所有的broker)、registBroker() 方法将broker信息注册到NameServer上、unregisterBroker()方法移除NameServer上的broker信息。

    4. RouteInfoManager 类管理了所有的broker、cluster集群、topicQueue主题队列以及broker存活的信息。

    展开全文
  • 一次性精通JVM JAVA虚拟机

    千人学习 2019-12-13 20:27:37
    为什么要学JVM 1、一切JAVA代码都运行在JVM之上,只有深入理解虚拟机才能写出更强大的代码,解决更深层次... 课程内容较多,不要一次性学太多,而是要循序渐进,坚持学习。            
  • HashMap源码解析

    千次阅读 多人点赞 2020-12-10 22:36:02
    崇祯五年十二月,余住西湖。大雪三日,湖中人鸟声俱绝。是日更定矣,余拏小舟,拥毳衣炉火,独往湖心亭看雪。雾凇沆砀,天与云与山与水,上下...本文分析HashMap源码和多线程下HashMap产生死锁的过程。后篇文章.

    崇祯五年十二月,余住西湖。大雪三日,湖中人鸟声俱绝。是日更定矣,余拏一小舟,拥毳衣炉火,独往湖心亭看雪。雾凇沆砀,天与云与山与水,上下一白。湖上影子,惟长堤一痕、湖心亭一点,与余舟一芥、舟中人两三粒而已。
    到亭上,有两人铺毡对坐,一童子烧酒炉正沸。见余,大喜曰:“湖中焉得更有此人!”拉余同饮。余强饮三大白而别。问其姓氏,是金陵人,客此。及下船,舟子喃喃曰:“莫说相公痴,更有痴似相公者!”

    ——张岱《湖心亭看雪》

    一、前言

    本文分析HashMap源码和多线程下HashMap产生死锁的过程。后面文章打算分析下HashMap死循环和ConcurentHashMap源码。

    二、HashMap总览

    2.1 HashMap需要掌握的几点

    • HashMap的成员变量及作用
    • Map的创建:HashMap()
    • 往Map中添加键值对:即put(Object key, Object value)方法
    • 获取Map中的单个对象:即get(Object key)方法
    • 删除Map中的对象:即remove(Object key)方法
    • 判断对象是否存在于Map中:containsKey(Object key)
    • 遍历Map中的对象:即keySet(),在实际中更常用的是增强型的for循环去做遍历
    • Map中对象的排序:主要取决于所采取的排序算法
    • HashMap死循环

    2.2 概述

    在JDK1.8之前,HashMap使用数组+链表实现,即使用链表处理冲突,同一hash值的节点都存储在一个链表里。但是当位于一个桶中的元素较多,即hash值相等的元素较多时,通过key值依次查找的效率较低。而JDK1.8中,HashMap采用数组+链表+红黑树实现,当链表长度超过阈值(8)时,将链表转换为红黑树,这样大大减少了查找时间。

    下图中代表jdk1.8之前的hashmap结构,左边部分即代表哈希表,也称为哈希数组,数组的每个元素都是一个单链表的头节点,链表是用来解决冲突的,如果不同的key映射到了数组的同一位置处,就将其放入单链表中。

    jdk1.8之前hashmap结构图
    jdk1.8之前的hashMap结构图

       jdk1.8之前的hashmap都采用上图的结构,都是基于一个数组和多个单链表,hash值冲突的时候,就将对应节点以链表的形式存储。如果在一个链表中查找其中一个节点时,将会花费O(n)的查找时间,会有很大的性能损失。到了jdk1.8,当同一个hash值的节点数不小于8时,不再采用单链表形式存储,而是采用红黑树,如下图所示:

    说明:上图很形象的展示了HashMap的数据结构(数组+链表+红黑树),桶中的结构可能是链表,也可能是红黑树,红黑树的引入是为了提高效率。 

    2.3 HashMap涉及到的数据结构

    2.3.1 链表

    Node是HashMap的一个内部类,实现了Map.Entry接口,本质上是一个映射(键值对)。上图中每一个黑圆点就是一个Node对象。来看具体代码:

    //Node是单向链表,它实现了Map.Entry接口
    static class Node<k,v> implements Map.Entry<k,v> {
        final int hash;
        final K key;
        V value;
        Node<k,v> next;
        //构造函数Hash值 键 值 下一个节点
        Node(int hash, K key, V value, Node<k,v> next) {
            this.hash = hash;
            this.key = key;
            this.value = value;
            this.next = next;
        }
     
        public final K getKey()        { return key; }
        public final V getValue()      { return value; }
        public final String toString() { return key + = + value; }
     
        public final int hashCode() {
            return Objects.hashCode(key) ^ Objects.hashCode(value);
        }
     
        public final V setValue(V newValue) {
            V oldValue = value;
            value = newValue;
            return oldValue;
        }
        //判断两个node是否相等,若key和value都相等,返回true。可以与自身比较为true
        public final boolean equals(Object o) {
            if (o == this)
                return true;
            if (o instanceof Map.Entry) {
                Map.Entry<!--?,?--> e = (Map.Entry<!--?,?-->)o;
                if (Objects.equals(key, e.getKey()) &&
                    Objects.equals(value, e.getValue()))
                    return true;
            }
            return false;
        }
    }

    可以看到,node中包含一个next变量,这个就是链表的关键点,hash结果相同的元素就是通过这个next进行关联的。 

    2.3.2 红黑树

    //红黑树
    static final class TreeNode<k,v> extends LinkedHashMap.Entry<k,v> {
        TreeNode<k,v> parent;  // 父节点
        TreeNode<k,v> left; //左子树
        TreeNode<k,v> right;//右子树
        TreeNode<k,v> prev;    // needed to unlink next upon deletion
        boolean red;    //颜色属性
        TreeNode(int hash, K key, V val, Node<k,v> next) {
            super(hash, key, val, next);
        }
     
        //返回当前节点的根节点
        final TreeNode<k,v> root() {
            for (TreeNode<k,v> r = this, p;;) {
                if ((p = r.parent) == null)
                    return r;
                r = p;
            }
        }
    }

    红黑树比链表多了四个变量,parent父节点、left左节点、right右节点、prev上一个同级节点,红黑树内容较多,不在赘述。 

    2.3.3 位桶

    transient Node<k,v>[] table;//存储(位桶)的数组

    HashMap类中有一个非常重要的字段,就是 Node[] table,即哈希桶数组,明显它是一个Node的数组。

         有了以上3个数据结构,只要有一点数据结构基础的人,都可以大致联想到HashMap的实现了。首先有一个每个元素都是链表(可能表述不准确)的数组,当添加一个元素(key-value)时,就首先计算元素key的hash值,以此确定插入数组中的位置,但是可能存在同一hash值的元素已经被放在数组同一位置了,这时就添加到同一hash值的元素的后面,他们在数组的同一位置,但是形成了链表,所以说数组存放的是链表。而当链表长度太长时,链表就转换为红黑树,这样大大提高了查找的效率。

    四、源码

    注意: 

    链表转红黑树,并不是达到8个Node节点的阈值就进行转换,而是要判断一下整个数据结构中的Node数量是否大于64,大于才会转,小于就会用扩容数组的方式代替红黑树的转换。

    在源码中的提现:

    在putVal中,当 binCount >= TREEIFY_THRESHOLD - 1时,会调用  treeifyBin(tab, hash);

     treeifyBin 方法,就是链表转红黑树方法,当 tab.length)< MIN_TREEIFY_CAPACITY  ,会扩容数组大小: 

     下面所说到的源码会忽略这一点,但我们一定要记住。

     4.1 类的继承关系

    public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable

    可以看到HashMap继承自父类(AbstractMap),实现了Map、Cloneable、Serializable接口。其中,Map接口定义了一组通用的操作;Cloneable接口则表示可以进行拷贝,在HashMap中,实现的是浅层次拷贝,即对拷贝对象的改变会影响被拷贝的对象;Serializable接口表示HashMap实现了序列化,即可以将HashMap对象保存至本地,之后可以恢复状态。 

    4.2  类的属性

    public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {
        // 序列号
        private static final long serialVersionUID = 362498820763181265L;    
        // 默认的初始容量是16
        static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;   
        // 最大容量
        static final int MAXIMUM_CAPACITY = 1 << 30; 
        // 默认的填充因子
        static final float DEFAULT_LOAD_FACTOR = 0.75f;
        // 当桶(bucket)上的结点数大于这个值时会转成红黑树;+对应的table的最小大小为64,即MIN_TREEIFY_CAPACITY ;这两个条件都满足,会链表会转红黑树
        static final int TREEIFY_THRESHOLD = 8; 
        // 当桶(bucket)上的结点数小于这个值时树转链表
        static final int UNTREEIFY_THRESHOLD = 6;
        // 桶中结构转化为红黑树对应的table的最小大小
        static final int MIN_TREEIFY_CAPACITY = 64;
        // 存储元素的数组,总是2的幂次倍
        transient Node<k,v>[] table; 
        // 存放具体元素的集
        transient Set<map.entry<k,v>> entrySet;
        // 存放元素的个数,注意这个不等于数组的长度。
        transient int size;
        // 每次扩容和更改map结构的计数器
        transient int modCount;   
        // 临界值 当实际大小(容量*填充因子)超过临界值时,会进行扩容
        int threshold;
        // 填充因子
        final float loadFactor;
    }

    说明:类的数据成员很重要,以上也解释得很详细了。

    4.3 类的构造函数

    4.3.1 HashMap(int, float)型构造函数

    public HashMap(int initialCapacity, float loadFactor) {
        // 初始容量不能小于0,否则报错
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                                initialCapacity);
        // 初始容量不能大于最大值,否则为最大值
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        // 填充因子不能小于或等于0,不能为非数字
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                                loadFactor);
        // 初始化填充因子                                        
        this.loadFactor = loadFactor;
        // 初始化threshold大小
        this.threshold = tableSizeFor(initialCapacity);    
    }

    说明:tableSizeFor(initialCapacity)返回大于initialCapacity的最小的二次幂数值。

    static final int tableSizeFor(int cap) {
        int n = cap - 1;
        n |= n >>> 1;
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }

    说明:>>> 操作符表示无符号右移,高位取0。
    tableSizeFor方法解析可以查看此链接:HashMap中 工具方法tableSizeFor的作用

    其他的构造方法,如:HashMap(int),HashMap(),HashMap(Map<? extends K>)就不在叙述,感兴趣可以看源码。

    4.4 hash算法

    4.4.1 源码分析

    在JDK 1.8中,hash方法如下: 

    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

    (1)首先获取对象的hashCode()值,然后将hashCode值右移16位,然后将右移后的值与原来的hashCode做异或运算,返回结果。(其中h>>>16,在JDK1.8中,优化了高位运算的算法,使用了零扩展,无论正数还是负数,都在高位插入0)。

    (2)在putVal源码中,我们通过(n-1)&hash获取该对象的键在hashmap中的位置。(其中hash的值就是(1)中获得的值)其中n表示的是hash桶数组的长度,并且该长度为2的n次方,这样(n-1)&hash就等价于hash%n。因为&运算的效率高于%运算

     n:hash槽数组大小;i:Node在数组中的索引值;

    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                    boolean evict) {
        ...
    
        if ((p = tab[i = (n - 1) & hash]) == null)//获取位置
            tab[i] = newNode(hash, key, value, null);
        ...
    }

    上述关键代码: i = (n - 1) & hash

    tab即是table,n是map集合的容量大小,hash是上面方法的返回值。因为通常声明map集合时不会指定大小,或者初始化的时候就创建一个容量很大的map对象,所以这个通过容量大小与key值进行hash的算法在开始的时候只会对低位进行计算,虽然容量的2进制高位一开始都是0,但是key的2进制高位通常是有值的,因此先在hash方法中将key的hashCode右移16位在与自身异或,使得高位也可以参与hash,更大程度上减少了碰撞率。

    下面举例说明下,n为table的长度。

    ^,按位运算符,异或 

    0 ^ 1 得 1
    1 ^ 1 得 0
    0 ^ 0 得 0
    1 ^ 0 得 1
    

     参考链接:浅谈HashMap中的hash算法

    4.4.2 扩容位置变化推演

    下面我们推演一下,当扩容时,原Node节点在新老数组中的位置变化:

     结论:扩容后,节点的位置有两种可能:

    1. 还在原来的数组索引上
    2. 原索引+扩容的长度

     4.4.3 HashMap 的容量为什么建议是 2的幂次方?

    到这里,我们提了一个关键的问题: HashMap 的容量为什么建议是 2的幂次方?正好可以和上面的话题接上。楼主就是这么设计的。

    为什么要 2 的幂次方呢?

    我们说,hash 算法的目的是为了让hash值均匀的分布在桶中(数组),那么,如何做到呢?试想一下,如果不使用 2 的幂次方作为数组的长度会怎么样?

    假设我们的数组长度是10,还是上面的公式:
    1010 & 101010100101001001000 结果:1000 = 8
    1010 & 101000101101001001001 结果:1000 = 8
    1010 & 101010101101101001010 结果: 1010 = 10
    1010 & 101100100111001101100 结果: 1000 = 8

    看到结果我们惊呆了,这种散列结果,会导致这些不同的key值全部进入到相同的插槽中,形成链表,性能急剧下降。

    所以说,我们一定要保证 & 中的二进制位全为 1,才能最大限度的利用 hash 值,并更好的散列,只有全是1 ,才能有更多的散列结果。如果是 1010,有的散列结果是永远都不会出现的,比如 0111,0101,1111,1110…,只要 & 之前的数有 0, 对应的 1 肯定就不会出现(因为只有都是1才会为1)。大大限制了散列的范围。

    4.5 重要方法分析 

    4.5.1 putVal方法

    首先说明,HashMap并没有直接提供putVal接口给用户调用,而是提供的put方法,而put方法就是通过putVal来插入元素的。

    public V put(K key, V value) {
        // 对key的hashCode()做hash 
        return putVal(hash(key), key, value, false, true);  
    } 

    putVal方法执行过程可以通过下图来理解:

     

    ①.判断键值对数组table[i]是否为空或为null,否则执行resize()进行扩容;
    ②.根据键值key计算hash值得到插入的数组索引i,如果table[i]==null,直接新建节点添加,转向⑥,如果table[i]不为空,转向③;
    ③.判断table[i]的首个元素是否和key一样,如果相同直接覆盖value,否则转向④,这里的相同指的是hashCode以及equals;
    ④.判断table[i] 是否为treeNode,即table[i] 是否是红黑树,如果是红黑树,则直接在树中插入键值对,否则转向⑤;
    ⑤.遍历table[i],判断链表长度是否大于8(且),大于8的话(且数组的长度大于64)把链表转换为红黑树,在红黑树中执行插入操作,否则进行链表的插入操作;遍历过程中若发现key已经存在直接覆盖value即可;
    ⑥.插入成功后,判断实际存在的键值对数量size是否超多了最大容量threshold,如果超过,进行扩容。

    具体源码如下:

    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                       boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        // 步骤①:tab为空则创建 
        // table未初始化或者长度为0,进行扩容
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        // 步骤②:计算index,并对null做处理  
        // (n - 1) & hash 确定元素存放在哪个桶中,桶为空,新生成结点放入桶中(此时,这个结点是放在数组中)
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        // 桶中已经存在元素
        else {
            Node<K,V> e; K k;
            // 步骤③:节点key存在,直接覆盖value 
            // 比较桶中第一个元素(数组中的结点)的hash值相等,key相等
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                    // 将第一个元素赋值给e,用e来记录
                    e = p;
            // 步骤④:判断该链为红黑树 
            // hash值不相等,即key不相等;为红黑树结点
            else if (p instanceof TreeNode)
                // 放入树中
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            // 步骤⑤:该链为链表 
            // 为链表结点
            else {
                // 在链表最末插入结点
                for (int binCount = 0; ; ++binCount) {
                    // 到达链表的尾部
                    if ((e = p.next) == null) {
                        // 在尾部插入新结点
                        p.next = newNode(hash, key, value, null);
                        // 结点数量达到阈值,转化为红黑树
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        // 跳出循环
                        break;
                    }
                    // 判断链表中结点的key值与插入的元素的key值是否相等
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        // 相等,跳出循环
                        break;
                    // 用于遍历桶中的链表,与前面的e = p.next组合,可以遍历链表
                    p = e;
                }
            }
            // 表示在桶中找到key值、hash值与插入元素相等的结点
            if (e != null) { 
                // 记录e的value
                V oldValue = e.value;
                // onlyIfAbsent为false或者旧值为null
                if (!onlyIfAbsent || oldValue == null)
                    //用新值替换旧值
                    e.value = value;
                // 访问后回调
                afterNodeAccess(e);
                // 返回旧值
                return oldValue;
            }
        }
        // 结构性修改
        ++modCount;
        // 步骤⑥:超过最大容量 就扩容 
        // 实际大小大于阈值则扩容
        if (++size > threshold)
            resize();
        // 插入后回调
        afterNodeInsertion(evict);
        return null;
    }

    HashMap的数据存储实现原理

    流程:

    1. 根据key计算得到key.hash = (h = k.hashCode()) ^ (h >>> 16);

    2. 根据key.hash计算得到桶数组的索引index = key.hash & (table.length - 1),这样就找到该key的存放位置了:
    ① 如果该位置没有数据,用该数据新生成一个节点保存新数据,返回null;
    ② 如果该位置有数据是一个红黑树,那么执行相应的插入 / 更新操作;
    ③ 如果该位置有数据是一个链表,分两种情况一是该链表没有这个节点,另一个是该链表上有这个节点,注意这里判断的依据是key.hash是否一样:

    如果该链表没有这个节点,那么采用尾插法新增节点保存新数据,返回null;如果该链表已经有这个节点了,那么找到该节点并更新新数据,返回老数据。

    注意:

    HashMap的put会返回key的上一次保存的数据,比如:

    HashMap<String, String> map = new HashMap<String, String>();
    System.out.println(map.put("a", "A")); // 打印null
    System.out.println(map.put("a", "AA")); // 打印A
    System.out.println(map.put("a", "AB")); // 打印AA

    4.5.2 getNode方法 

    说明:HashMap同样并没有直接提供getNode接口给用户调用,而是提供的get方法,而get方法就是通过getNode来取得元素的。

    public V get(Object key) {
        Node<k,v> e;
        return (e = getNode(hash(key), key)) == null ? null : e.value;
    }
    final Node<K,V> getNode(int hash, Object key) {
        Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
        // table已经初始化,长度大于0,根据hash寻找table中的项也不为空
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (first = tab[(n - 1) & hash]) != null) {
            // 桶中第一项(数组元素)相等
            if (first.hash == hash && // always check first node
                ((k = first.key) == key || (key != null && key.equals(k))))
                return first;
            // 桶中不止一个结点
            if ((e = first.next) != null) {
                // 为红黑树结点
                if (first instanceof TreeNode)
                    // 在红黑树中查找
                    return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                // 否则,在链表中查找
                do {
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        return null;
    }

     4.5.3 resize方法

    ①.在jdk1.8中,resize方法是在hashmap中的键值对大于阀值时或者初始化时,就调用resize方法进行扩容;
    ②.每次扩展的时候,都是扩展2倍;
    ③.扩展后Node对象的位置要么在原位置,要么移动到原偏移量两倍的位置。

    final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table;//oldTab指向hash桶数组
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        int oldThr = threshold;
        int newCap, newThr = 0;
        if (oldCap > 0) {//如果oldCap不为空的话,就是hash桶数组不为空
            if (oldCap >= MAXIMUM_CAPACITY) {//如果大于最大容量了,就赋值为整数最大的阀值
                threshold = Integer.MAX_VALUE;
                return oldTab;//返回
            }//如果当前hash桶数组的长度在扩容后仍然小于最大容量 并且oldCap大于默认值16
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1; // double threshold 双倍扩容阀值threshold
        }
        else if (oldThr > 0) // initial capacity was placed in threshold
            newCap = oldThr;
        else {               // zero initial threshold signifies using defaults
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
        if (newThr == 0) {
            float ft = (float)newCap * loadFactor;
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);
        }
        threshold = newThr;
        @SuppressWarnings({"rawtypes","unchecked"})
            Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];//新建hash桶数组
        table = newTab;//将新数组的值复制给旧的hash桶数组
        if (oldTab != null) {//进行扩容操作,复制Node对象值到新的hash桶数组
            for (int j = 0; j < oldCap; ++j) {
                Node<K,V> e;
                if ((e = oldTab[j]) != null) {//如果旧的hash桶数组在j结点处不为空,复制给e
                    oldTab[j] = null;//将旧的hash桶数组在j结点处设置为空,方便gc
                    if (e.next == null)//如果e后面没有Node结点
                        newTab[e.hash & (newCap - 1)] = e;//直接对e的hash值对新的数组长度求模获得存储位置
                    else if (e instanceof TreeNode)//如果e是红黑树的类型,那么添加到红黑树中
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    else { // preserve order
                        Node<K,V> loHead = null, loTail = null;
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;
                        do {
                            next = e.next;//将Node结点的next赋值给next
                            if ((e.hash & oldCap) == 0) {//如果结点e的hash值与原hash桶数组的长度作与运算为0
                                if (loTail == null)//如果loTail为null
                                    loHead = e;//将e结点赋值给loHead
                                else
                                    loTail.next = e;//否则将e赋值给loTail.next
                                loTail = e;//然后将e复制给loTail
                            }
                            else {//如果结点e的hash值与原hash桶数组的长度作与运算不为0
                                if (hiTail == null)//如果hiTail为null
                                    hiHead = e;//将e赋值给hiHead
                                else
                                    hiTail.next = e;//如果hiTail不为空,将e复制给hiTail.next
                                hiTail = e;//将e复制个hiTail
                            }
                        } while ((e = next) != null);//直到e为空
                        if (loTail != null) {//如果loTail不为空
                            loTail.next = null;//将loTail.next设置为空
                            newTab[j] = loHead;//将loHead赋值给新的hash桶数组[j]处
                        }
                        if (hiTail != null) {//如果hiTail不为空
                            hiTail.next = null;//将hiTail.next赋值为空
                            newTab[j + oldCap] = hiHead;//将hiHead赋值给新的hash桶数组[j+旧hash桶数组长度]
                        }
                    }
                }
            }
        }
        return newTab;
    }

    扩容时如果Node为TreeNode(红黑树的节点),具体代码解析:HashMap-split()方法源码简读(JDK1.8)

    五、HashMap死循环

     由于章幅有限,我将在后续文章详细讲解。

    展开全文
  • 深入理解Ribbon之源码解析

    万次阅读 多人点赞 2017-07-08 14:48:05
    Ribbon是Netflix公司开源的个负载均衡的项目,它属于上述的第二种,是个客户端负载均衡器,运行在客户端上。它是个经过了云端测试的IPC库,可以很好地控制HTTP和TCP客户端的一些行为。 Feign已经默认使用.

    转载请标明出处:
    https://blog.csdn.net/forezp/article/details/74820899
    本文出自方志朋的博客

    个人博客纯净版:https://www.fangzhipeng.com/springcloud/2017/08/11/Ribbon-resources.html

    什么是Ribbon

    Ribbon是Netflix公司开源的一个负载均衡的项目,它属于上述的第二种,是一个客户端负载均衡器,运行在客户端上。它是一个经过了云端测试的IPC库,可以很好地控制HTTP和TCP客户端的一些行为。 Feign已经默认使用了Ribbon。

    • 负载均衡
    • 容错
    • 多协议(HTTP,TCP,UDP)支持异步和反应模型
    • 缓存和批处理

    RestTemplate和Ribbon相结合

    Ribbon在Netflix组件是非常重要的一个组件,在Zuul中使用Ribbon做负载均衡,以及Feign组件的结合等。在Spring Cloud 中,作为开发中,做的最多的可能是将RestTemplate和Ribbon相结合,你可能会这样写:

    @Configuration
    public class RibbonConfig {
        @Bean
        @LoadBalanced
        RestTemplate restTemplate() {
            return new RestTemplate();
        }
    }
    
    

    消费另外一个的服务的接口,差不多是这样的:

    
    @Service
    public class RibbonService {
        @Autowired
        RestTemplate restTemplate;
        public String hi(String name) {
            return restTemplate.getForObject("http://eureka-client/hi?name="+name,String.class);
        }
    }
    
    

    深入理解Ribbon

    LoadBalancerClient

    在Riibon中一个非常重要的组件为LoadBalancerClient,它作为负载均衡的一个客户端。它在spring-cloud-commons包下:
    的LoadBalancerClient是一个接口,它继承ServiceInstanceChooser,它的实现类是RibbonLoadBalancerClient,这三者之间的关系如下图:

    在这里插入图片描述

    其中LoadBalancerClient接口,有如下三个方法,其中excute()为执行请求,reconstructURI()用来重构url:

    public interface LoadBalancerClient extends ServiceInstanceChooser {
      <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException;
      <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException;
      URI reconstructURI(ServiceInstance instance, URI original);
    
    }
    
    

    ServiceInstanceChooser接口,主要有一个方法,用来根据serviceId来获取ServiceInstance,代码如下:

    public interface ServiceInstanceChooser {
    
        ServiceInstance choose(String serviceId);
    }
    
    

    LoadBalancerClient的实现类为RibbonLoadBalancerClient,这个类是非常重要的一个类,最终的负载均衡的请求处理,由它来执行。它的部分源码如下:

    public class RibbonLoadBalancerClient implements LoadBalancerClient {
    
    ...//省略代码
    
    @Override
    	public ServiceInstance choose(String serviceId) {
    		Server server = getServer(serviceId);
    		if (server == null) {
    			return null;
    		}
    		return new RibbonServer(serviceId, server, isSecure(server, serviceId),
    				serverIntrospector(serviceId).getMetadata(server));
    	}
    
    
    
    protected Server getServer(String serviceId) {
    		return getServer(getLoadBalancer(serviceId));
       }
    
    
    protected Server getServer(ILoadBalancer loadBalancer) {
    		if (loadBalancer == null) {
    			return null;
    		}
    		return loadBalancer.chooseServer("default"); // TODO: better handling of key
    	}
    
    
    protected ILoadBalancer getLoadBalancer(String serviceId) {
    		return this.clientFactory.getLoadBalancer(serviceId);
    	}
    	
    	...//省略代码
    
    

    在RibbonLoadBalancerClient的源码中,其中choose()方法是选择具体服务实例的一个方法。该方法通过getServer()方法去获取实例,经过源码跟踪,最终交给了ILoadBalancer类去选择服务实例。

    ILoadBalancer在ribbon-loadbalancer的jar包下,它是定义了实现软件负载均衡的一个接口,它需要一组可供选择的服务注册列表信息,以及根据特定方法去选择服务,它的源码如下 :

    public interface ILoadBalancer {
    
        public void addServers(List<Server> newServers);
        public Server chooseServer(Object key);
        public void markServerDown(Server server);
        public List<Server> getReachableServers();
        public List<Server> getAllServers();
    }
    
    
    

    其中,addServers()方法是添加一个Server集合;chooseServer()方法是根据key去获取Server;markServerDown()方法用来标记某个服务下线;getReachableServers()获取可用的Server集合;getAllServers()获取所有的Server集合。

    DynamicServerListLoadBalancer

    它的继承类为BaseLoadBalancer,它的实现类为DynamicServerListLoadBalancer,这三者之间的关系如下:

    在这里插入图片描述

    查看上述三个类的源码,可用发现,配置以下信息,IClientConfig、IRule、IPing、ServerList、ServerListFilter和ILoadBalancer,查看BaseLoadBalancer类,它默认的情况下,实现了以下配置:

    • IClientConfig ribbonClientConfig: DefaultClientConfigImpl配置
    • IRule ribbonRule: RoundRobinRule 路由策略
    • IPing ribbonPing: DummyPing
    • ServerList ribbonServerList: ConfigurationBasedServerList
    • ServerListFilter ribbonServerListFilter: ZonePreferenceServerListFilter
    • ILoadBalancer ribbonLoadBalancer: ZoneAwareLoadBalancer

    IClientConfig 用于对客户端或者负载均衡的配置,它的默认实现类为DefaultClientConfigImpl。

    IRule用于复杂均衡的策略,它有三个方法,其中choose()是根据key 来获取server,setLoadBalancer()和getLoadBalancer()是用来设置和获取ILoadBalancer的,它的源码如下:

    public interface IRule{
    
        public Server choose(Object key);
        
        public void setLoadBalancer(ILoadBalancer lb);
        
        public ILoadBalancer getLoadBalancer();    
    }
    
    
    

    IRule有很多默认的实现类,这些实现类根据不同的算法和逻辑来处理负载均衡。Ribbon实现的IRule有一下。在大多数情况下,这些默认的实现类是可以满足需求的,如果有特性的需求,可以自己实现。

    • BestAvailableRule 选择最小请求数

    • ClientConfigEnabledRoundRobinRule 轮询

    • RandomRule 随机选择一个server

    • RoundRobinRule 轮询选择server

    • RetryRule 根据轮询的方式重试

    • WeightedResponseTimeRule 根据响应时间去分配一个weight ,weight越低,被选择的可能性就越低

    • ZoneAvoidanceRule 根据server的zone区域和可用性来轮询选择

    在这里插入图片描述

    IPing是用来想server发生"ping",来判断该server是否有响应,从而判断该server是否可用。它有一个isAlive()方法,它的源码如下:

    public interface IPing {
        public boolean isAlive(Server server);
    }
    
    
    

    IPing的实现类有PingUrl、PingConstant、NoOpPing、DummyPing和NIWSDiscoveryPing。它门之间的关系如下:

    在这里插入图片描述

    • PingUrl 真实的去ping 某个url,判断其是否alive
    • PingConstant 固定返回某服务是否可用,默认返回true,即可用
    • NoOpPing 不去ping,直接返回true,即可用。
    • DummyPing 直接返回true,并实现了initWithNiwsConfig方法。
    • NIWSDiscoveryPing,根据DiscoveryEnabledServer的InstanceInfo的InstanceStatus去判断,如果为InstanceStatus.UP,则为可用,否则不可用。

    ServerList是定义获取所有的server的注册列表信息的接口,它的代码如下:

    public interface ServerList<T extends Server> {
    
        public List<T> getInitialListOfServers();
        public List<T> getUpdatedListOfServers();   
    
    }
    
    

    ServerListFilter接口,定于了可根据配置去过滤或者根据特性动态获取符合条件的server列表的方法,代码如下:

    public interface ServerListFilter<T extends Server> {
    
        public List<T> getFilteredListOfServers(List<T> servers);
    
    }
    
    
    

    阅读DynamicServerListLoadBalancer的源码,DynamicServerListLoadBalancer的构造函数中有个initWithNiwsConfig()方法。在改方法中,经过一系列的初始化配置,最终执行了restOfInit()方法。其代码如下:

    
     public DynamicServerListLoadBalancer(IClientConfig clientConfig) {
            initWithNiwsConfig(clientConfig);
        }
        
        @Override
        public void initWithNiwsConfig(IClientConfig clientConfig) {
            try {
                super.initWithNiwsConfig(clientConfig);
                String niwsServerListClassName = clientConfig.getPropertyAsString(
                        CommonClientConfigKey.NIWSServerListClassName,
                        DefaultClientConfigImpl.DEFAULT_SEVER_LIST_CLASS);
    
                ServerList<T> niwsServerListImpl = (ServerList<T>) ClientFactory
                        .instantiateInstanceWithClientConfig(niwsServerListClassName, clientConfig);
                this.serverListImpl = niwsServerListImpl;
    
                if (niwsServerListImpl instanceof AbstractServerList) {
                    AbstractServerListFilter<T> niwsFilter = ((AbstractServerList) niwsServerListImpl)
                            .getFilterImpl(clientConfig);
                    niwsFilter.setLoadBalancerStats(getLoadBalancerStats());
                    this.filter = niwsFilter;
                }
    
                String serverListUpdaterClassName = clientConfig.getPropertyAsString(
                        CommonClientConfigKey.ServerListUpdaterClassName,
                        DefaultClientConfigImpl.DEFAULT_SERVER_LIST_UPDATER_CLASS
                );
    
                this.serverListUpdater = (ServerListUpdater) ClientFactory
                        .instantiateInstanceWithClientConfig(serverListUpdaterClassName, clientConfig);
    
                restOfInit(clientConfig);
            } catch (Exception e) {
                throw new RuntimeException(
                        "Exception while initializing NIWSDiscoveryLoadBalancer:"
                                + clientConfig.getClientName()
                                + ", niwsClientConfig:" + clientConfig, e);
            }
        }
    
    

    在restOfInit()方法上,有一个 updateListOfServers()的方法,该方法是用来获取所有的ServerList的。

     void restOfInit(IClientConfig clientConfig) {
            boolean primeConnection = this.isEnablePrimingConnections();
            // turn this off to avoid duplicated asynchronous priming done in BaseLoadBalancer.setServerList()
            this.setEnablePrimingConnections(false);
            enableAndInitLearnNewServersFeature();
    
            updateListOfServers();
            if (primeConnection && this.getPrimeConnections() != null) {
                this.getPrimeConnections()
                        .primeConnections(getReachableServers());
            }
            this.setEnablePrimingConnections(primeConnection);
            LOGGER.info("DynamicServerListLoadBalancer for client {} initialized: {}", clientConfig.getClientName(), this.toString());
        }
    
    

    进一步跟踪updateListOfServers()方法的源码,最终由serverListImpl.getUpdatedListOfServers()获取所有的服务列表的,代码如下:

       @VisibleForTesting
        public void updateListOfServers() {
            List<T> servers = new ArrayList<T>();
            if (serverListImpl != null) {
                servers = serverListImpl.getUpdatedListOfServers();
                LOGGER.debug("List of Servers for {} obtained from Discovery client: {}",
                        getIdentifier(), servers);
    
                if (filter != null) {
                    servers = filter.getFilteredListOfServers(servers);
                    LOGGER.debug("Filtered List of Servers for {} obtained from Discovery client: {}",
                            getIdentifier(), servers);
                }
            }
            updateAllServerList(servers);
        }
    
    

    而serverListImpl是ServerList接口的具体实现类。跟踪代码,ServerList的实现类为DiscoveryEnabledNIWSServerList,在ribbon-eureka.jar的com.netflix.niws.loadbalancer下。其中DiscoveryEnabledNIWSServerList有 getInitialListOfServers()和getUpdatedListOfServers()方法,具体代码如下:

    @Override
        public List<DiscoveryEnabledServer> getInitialListOfServers(){
            return obtainServersViaDiscovery();
        }
    
        @Override
        public List<DiscoveryEnabledServer> getUpdatedListOfServers(){
            return obtainServersViaDiscovery();
        }
    
    

    继续跟踪源码,obtainServersViaDiscovery(),是根据eurekaClientProvider.get()来回去EurekaClient,再根据EurekaClient来获取注册列表信息,代码如下:

    
     private List<DiscoveryEnabledServer> obtainServersViaDiscovery() {
            List<DiscoveryEnabledServer> serverList = new ArrayList<DiscoveryEnabledServer>();
    
            if (eurekaClientProvider == null || eurekaClientProvider.get() == null) {
                logger.warn("EurekaClient has not been initialized yet, returning an empty list");
                return new ArrayList<DiscoveryEnabledServer>();
            }
    
            EurekaClient eurekaClient = eurekaClientProvider.get();
            if (vipAddresses!=null){
                for (String vipAddress : vipAddresses.split(",")) {
                    // if targetRegion is null, it will be interpreted as the same region of client
                    List<InstanceInfo> listOfInstanceInfo = eurekaClient.getInstancesByVipAddress(vipAddress, isSecure, targetRegion);
                    for (InstanceInfo ii : listOfInstanceInfo) {
                        if (ii.getStatus().equals(InstanceStatus.UP)) {
    
                            if(shouldUseOverridePort){
                                if(logger.isDebugEnabled()){
                                    logger.debug("Overriding port on client name: " + clientName + " to " + overridePort);
                                }
    
                                // copy is necessary since the InstanceInfo builder just uses the original reference,
                                // and we don't want to corrupt the global eureka copy of the object which may be
                                // used by other clients in our system
                                InstanceInfo copy = new InstanceInfo(ii);
    
                                if(isSecure){
                                    ii = new InstanceInfo.Builder(copy).setSecurePort(overridePort).build();
                                }else{
                                    ii = new InstanceInfo.Builder(copy).setPort(overridePort).build();
                                }
                            }
    
                            DiscoveryEnabledServer des = new DiscoveryEnabledServer(ii, isSecure, shouldUseIpAddr);
                            des.setZone(DiscoveryClient.getZone(ii));
                            serverList.add(des);
                        }
                    }
                    if (serverList.size()>0 && prioritizeVipAddressBasedServers){
                        break; // if the current vipAddress has servers, we dont use subsequent vipAddress based servers
                    }
                }
            }
            return serverList;
        }
    

    其中eurekaClientProvider的实现类是LegacyEurekaClientProvider,它是一个获取eurekaClient类,通过静态的方法去获取eurekaClient,其代码如下:

    class LegacyEurekaClientProvider implements Provider<EurekaClient> {
    
        private volatile EurekaClient eurekaClient;
    
        @Override
        public synchronized EurekaClient get() {
            if (eurekaClient == null) {
                eurekaClient = DiscoveryManager.getInstance().getDiscoveryClient();
            }
    
            return eurekaClient;
        }
    }
    
    

    EurekaClient的实现类为DiscoveryClient,在之前已经分析了它具有服务注册、获取服务注册列表等的全部功能。

    由此可见,负载均衡器是从EurekaClient获取服务信息,并根据IRule去路由,并且根据IPing去判断服务的可用性。

    那么现在还有个问题,负载均衡器多久一次去获取一次从Eureka Client获取注册信息呢。

    在BaseLoadBalancer类下,BaseLoadBalancer的构造函数,该构造函数开启了一个PingTask任务,代码如下:

       public BaseLoadBalancer(String name, IRule rule, LoadBalancerStats stats,
                IPing ping, IPingStrategy pingStrategy) {
    	    ...//代码省略
            setupPingTask();
             ...//代码省略
        }
    
    

    setupPingTask()的具体代码逻辑,它开启了ShutdownEnabledTimer执行PingTask任务,在默认情况下pingIntervalSeconds为10,即每10秒钟,想EurekaClient发送一次"ping"。

        void setupPingTask() {
            if (canSkipPing()) {
                return;
            }
            if (lbTimer != null) {
                lbTimer.cancel();
            }
            lbTimer = new ShutdownEnabledTimer("NFLoadBalancer-PingTimer-" + name,
                    true);
            lbTimer.schedule(new PingTask(), 0, pingIntervalSeconds * 1000);
            forceQuickPing();
        }
    
    

    PingTask源码,即new一个Pinger对象,并执行runPinger()方法。

    class PingTask extends TimerTask {
            public void run() {
                try {
                	new Pinger(pingStrategy).runPinger();
                } catch (Exception e) {
                    logger.error("LoadBalancer [{}]: Error pinging", name, e);
                }
            }
        }
    
    

    查看Pinger的runPinger()方法,最终根据 pingerStrategy.pingServers(ping, allServers)来获取服务的可用性,如果该返回结果,如之前相同,则不去向EurekaClient获取注册列表,如果不同则通知ServerStatusChangeListener或者changeListeners发生了改变,进行更新或者重新拉取。

      public void runPinger() throws Exception {
                if (!pingInProgress.compareAndSet(false, true)) { 
                    return; // Ping in progress - nothing to do
                }
                
                // we are "in" - we get to Ping
    
                Server[] allServers = null;
                boolean[] results = null;
    
                Lock allLock = null;
                Lock upLock = null;
    
                try {
                    /*
                     * The readLock should be free unless an addServer operation is
                     * going on...
                     */
                    allLock = allServerLock.readLock();
                    allLock.lock();
                    allServers = allServerList.toArray(new Server[allServerList.size()]);
                    allLock.unlock();
    
                    int numCandidates = allServers.length;
                    results = pingerStrategy.pingServers(ping, allServers);
    
                    final List<Server> newUpList = new ArrayList<Server>();
                    final List<Server> changedServers = new ArrayList<Server>();
    
                    for (int i = 0; i < numCandidates; i++) {
                        boolean isAlive = results[i];
                        Server svr = allServers[i];
                        boolean oldIsAlive = svr.isAlive();
    
                        svr.setAlive(isAlive);
    
                        if (oldIsAlive != isAlive) {
                            changedServers.add(svr);
                            logger.debug("LoadBalancer [{}]:  Server [{}] status changed to {}", 
                        		name, svr.getId(), (isAlive ? "ALIVE" : "DEAD"));
                        }
    
                        if (isAlive) {
                            newUpList.add(svr);
                        }
                    }
                    upLock = upServerLock.writeLock();
                    upLock.lock();
                    upServerList = newUpList;
                    upLock.unlock();
    
                    notifyServerStatusChangeListener(changedServers);
                } finally {
                    pingInProgress.set(false);
                }
            }
    
    

    由此可见,LoadBalancerClient是在初始化的时候,会向Eureka回去服务注册列表,并且向通过10s一次向EurekaClient发送“ping”,来判断服务的可用性,如果服务的可用性发生了改变或者服务数量和之前的不一致,则更新或者重新拉取。LoadBalancerClient有了这些服务注册列表,就可以根据具体的IRule来进行负载均衡。

    RestTemplate是如何和Ribbon结合的

    最后,回答问题的本质,为什么在RestTemplate加一个@LoadBalance注解就可可以开启负载均衡呢?

     @LoadBalanced
        RestTemplate restTemplate() {
            return new RestTemplate();
        }
    

    全局搜索ctr+shift+f @LoadBalanced有哪些类用到了LoadBalanced有哪些类用到了, 发现LoadBalancerAutoConfiguration类,即LoadBalancer自动配置类。

    @Configuration
    @ConditionalOnClass(RestTemplate.class)
    @ConditionalOnBean(LoadBalancerClient.class)
    @EnableConfigurationProperties(LoadBalancerRetryProperties.class)
    public class LoadBalancerAutoConfiguration {
    
    @LoadBalanced
    	@Autowired(required = false)
    	private List<RestTemplate> restTemplates = Collections.emptyList();
    }
    	@Bean
    	public SmartInitializingSingleton loadBalancedRestTemplateInitializer(
    			final List<RestTemplateCustomizer> customizers) {
    		return new SmartInitializingSingleton() {
    			@Override
    			public void afterSingletonsInstantiated() {
    				for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
    					for (RestTemplateCustomizer customizer : customizers) {
    						customizer.customize(restTemplate);
    					}
    				}
    			}
    		};
    	}
    	
    	
    	@Configuration
    	@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
    	static class LoadBalancerInterceptorConfig {
    		@Bean
    		public LoadBalancerInterceptor ribbonInterceptor(
    				LoadBalancerClient loadBalancerClient,
    				LoadBalancerRequestFactory requestFactory) {
    			return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
    		}
    
    		@Bean
    		@ConditionalOnMissingBean
    		public RestTemplateCustomizer restTemplateCustomizer(
    				final LoadBalancerInterceptor loadBalancerInterceptor) {
    			return new RestTemplateCustomizer() {
    				@Override
    				public void customize(RestTemplate restTemplate) {
    					List<ClientHttpRequestInterceptor> list = new ArrayList<>(
    							restTemplate.getInterceptors());
    					list.add(loadBalancerInterceptor);
    					restTemplate.setInterceptors(list);
    				}
    			};
    		}
    	}
    
    }
    

    在该类中,首先维护了一个被@LoadBalanced修饰的RestTemplate对象的List,在初始化的过程中,通过调用customizer.customize(restTemplate)方法来给RestTemplate增加拦截器LoadBalancerInterceptor。

    而LoadBalancerInterceptor,用于实时拦截,在LoadBalancerInterceptor这里实现来负载均衡。LoadBalancerInterceptor的拦截方法如下:

    @Override
    	public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
    			final ClientHttpRequestExecution execution) throws IOException {
    		final URI originalUri = request.getURI();
    		String serviceName = originalUri.getHost();
    		Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);
    		return this.loadBalancer.execute(serviceName, requestFactory.createRequest(request, body, execution));
    	}
    
    

    总结

    综上所述,Ribbon的负载均衡,主要通过LoadBalancerClient来实现的,而LoadBalancerClient具体交给了ILoadBalancer来处理,ILoadBalancer通过配置IRule、IPing等信息,并向EurekaClient获取注册列表的信息,并默认10秒一次向EurekaClient发送“ping”,进而检查是否更新服务列表,最后,得到注册列表后,ILoadBalancer根据IRule的策略进行负载均衡。

    而RestTemplate 被@LoadBalance注解后,能过用负载均衡,主要是维护了一个被@LoadBalance注解的RestTemplate列表,并给列表中的RestTemplate添加拦截器,进而交给负载均衡器去处理。

    更多阅读

    史上最简单的 SpringCloud 教程汇总

    SpringBoot教程汇总

    Java面试题系列汇总

    关注我的公众号

    精彩内容不能错过!


    扫码关注公众号有惊喜

    (转载本站文章请注明作者和出处 方志朋的博客

    展开全文
  • 2.9 Vector 源码解析

    千次阅读 2021-01-21 23:10:05
    和ArrayList一样,Vector也是List接口的个实现类。 其中List接口主要实现类有ArrayLIst,LinkedList,Vector,Stack。 其中后两者用的特别少。 2.9.1 vector组成 和ArrayList基本一样。 //存放元素的数组 ...
  • RocketMQ源码解析-开篇

    千次阅读 2020-08-07 09:14:48
    其实嘛在第一次使用 RocketMQ 后就有对源码研究的想法,并不是说它相比与其他 MQ 有非常独特的优势,而是肥壕觉得有这么简单的几个理由吧,还是很值得我们研读一番滴~ 基于 Java 栈的中间件 作为阿里系的开源产品...
  • ArrayList(JDK1.8)源码解析

    万次阅读 多人点赞 2019-08-08 09:18:05
    既然是看源码,那我们要怎么看个类的源码呢?这里我推荐的方法是: 1)看继承结构 ​ 看这个类的层次结构,处于个什么位置,可以在自己心里有个大概的了解。 2)看构造方法 ​ 在构造方法中,看做了哪些事情,...
  • OkHttp源码解析(小白必看,建议收藏)

    千次阅读 多人点赞 2021-07-29 18:31:43
    在两年前还是专科的时候为了找工作看过Okhttp3的部分源码,记了点笔记,最近又到本科找工作阶段了,翻出来自己以前的笔记。整理出篇文章,希望能够对各位产生帮助。(对初学者极其友好) 计蒙创作不易,未入驻的...
  • Cocos creator 源码解析()

    千次阅读 2020-04-14 11:32:21
    加上最近用cocos比较多,底层看的比较少,准备开始对全部的源码一次深刻的解读,此篇文章也是方便我自己以后查看。源码已经添加了对应方法的解释,在此基础上加入我个人的解释。 PS: 本人是web方向的,所以基本...
  • Lucene检索源码解析(上)

    万次阅读 多人点赞 2019-04-22 18:21:48
    有了Lucene得分公式(戳这里看详情)的基础,我们现在先跳过写索引的步骤,直接解析查询这块儿的代码(还是基于5.5.0)。另外由于内容实在太多,所以文章分为上下两部分介绍,上部分主要介绍实际检索前的一些处理,...
  • 从本期开始,Gauss松鼠会将陆续推出openGauss数据库源码解析系列文章,带你解析openGauss各功能模块的源代码逻辑和实现原理。该系列文章主要面向openGauss内核开发者及研究人员。 接下来会先向大家概要介绍openGauss...
  • MySQL 8.0 MVCC 源码解析

    千次阅读 多人点赞 2020-09-03 11:46:32
    以上内容是对于 RR 级别来说,而对于 RC 级别,其实整个过程几乎一样,唯一不同的是生成 ReadView 的时机,RR 级别只在事务第一次 select 时生成一次,之后一直使用该 ReadView。而 RC 级别则在每次 select 时,...
  • KafkaProducer源码解析

    千次阅读 2022-03-15 16:32:32
    、为什么需要消息系统 1.解耦:  允许你独立的扩展或修改两边的处理过程,只要确保它们遵守同样的接口约束。 2.冗余:  消息队列把数据进行持久化直到它们已经被完全处理,通过这方式规避了数据丢失风险。许多...
  • 之前发过个帖子,但是那个帖子有点问题我就重新发个吧,下面的源码是我从今年开始不断整理源码区和其他网站上的安卓例子源码,目前总共有810套左右,根据实现的功能被我分成了100多个类,总共接近2.5G,还在不断...
  • Kafka消费者流程源码解析

    千次阅读 2022-03-11 21:39:07
    // 消费者抓取器 // fetch.min.bytes 最小一次抓取的字节数 // fetch.max.bytes 最大抓取的字节数 // fetch.max.wait.ms 抓取的等待时间 // max.poll.records 一次性返回的最大条数 this.fetcher = new Fetcher( ...
  • MyBatis 源码分析 - 映射文件解析过程

    千次阅读 多人点赞 2021-04-04 00:04:39
    文章目录1.简介2.映射文件解析过程分析2.1 解析映射...所以我将映射文件解析过程的分析内容从上篇文章中抽取出来,独立成文,于是就有了本篇文章。在本篇文章中,我将分析映射文件中出现的一些及节点,比如<cache
  • HashMap(JDK1.8)源码解析

    万次阅读 多人点赞 2019-08-08 09:28:16
    在JDK1.8之前,HashMap采用数组+链表实现,即使用链表处理冲突,同一hash值的节点都存储在个链表里。但是当位于个桶中的元素较多,即hash值相等的元素较多时,通过key值依次查找的效率较低。而JDK1.8中,为了...
  • 前言json报文相信大家都接触过,对于前段JavaScript来说,它是最方便处理的数据格式,而对于后端应用来说,解析json报文并没有xml格式来的那么清晰明了,尤其是对于通用的处理来说很难做到,这里在参考了阿里巴巴的...
  • Kafka源码深度解析

    千次阅读 2018-08-30 16:48:35
    Kafka源码深度解析-系列1 -消息队列的策略与语义 -Kafka关键概念介绍  -消息队列的各种策略与语义 作为个消息队列,Kafka在业界已经相当有名。相对传统的RabbitMq/ActiveMq,Kafka天生就是分布式的,支持数据...
  • okhttp源码解析

    万次阅读 多人点赞 2017-11-03 15:01:17
    OkHttp是个非常优秀的网络请求框架,已被谷歌加入到Android的源码中。目前比较流行的Retrofit也是默认使用OkHttp的。所以OkHttp的源码个不容错过的学习资源,学习源码之前,务必熟练使用这个框架,否则就是跟...
  • 当有请求过来时,每个节点都会去数据库查询按照初始的DB中的step去更新最大id,从而获取到个号段,然后每个节点当第个号段用到超过10%的时候再异步准备第二个号段。所以按照图中的理解可以认为左中右三个节点...
  • Android之EasyPermissions源码解析

    万次阅读 2018-08-20 18:16:25
    我们知道在Android中想要申请权限就需要在AndroidManifest配置文件中通过uses-permission标签设置申请的权限,通过这种方式申请权限固然方便,但在安全方面却不高,比如开发者申请获取用户隐私的权限,这样用户在...
  • 1.【进阶】RecyclerView源码解析()——绘制流程 2.【进阶】RecyclerView源码解析(二)——缓存机制 3.【进阶】RecyclerView源码解析(三)——深度解析缓存机制 引言 自从Google出了RecyclerView后,基本上...
  • 以太坊Geth Trie源码解析

    千次阅读 2021-07-20 15:06:00
    Merkle Patricia Trie 是种经过改良的、融合了默克尔树(Merkle Trie)和前缀树(Patricia Trie)两种树结构优点的数据结构,是以太坊中用来存储键值数据对(Key, Value)的重要树形数据结构。 MPT树具有以下几个...
  • Spring MVC 请求执行流程的源码深度解析【两万字】

    千次阅读 多人点赞 2021-06-22 10:59:36
    基于最新Spring 5.x,详细介绍了Spring MVC 请求的执行流程源码,给出了更加详细的Spring MVC请求执行流程步骤总结,以及详细的执行流程图。
  • 相信大家应该经常会用到OpenCV中的函数...2)INTER_LINEAR - 双线插值法(默认) 3)INTER_AREA - 基于局部像素的重采样(resampling using pixel area relation)。对于图像抽取(image decimation)来说,这可能
  • ThreadLocal 源码详细解析

    千次阅读 2018-04-20 21:49:51
    写这篇文章不在计划之内, 主要是分析到Handler消息机制一文中,牵涉到ThreadLocal内容,一次全部写完文章过长,所以打算单独摘出来梳理成为一篇文章。 引言 在我们日常开发中用到ThreadLocal的地方很多,在...
  • 简述: 好久没有发布原创文章,一如既往,今天开始Kotlin浅谈系列的第十讲,起来探索Kotlin中的序列。序列(Sequences)实际上是对应Java8中的Stream的翻版。从之前博客可以了解到Kotlin定义了很多操作集合的API,没...
  • Netty框架源码解析

    万次阅读 多人点赞 2019-01-20 14:10:18
    构建以批次为单位,一次构建多组,分别处理accept、io不同事件。 main对应的accept;sub对应的是IO; EventLoopGroup实现了EventExcutor接口,通过上层父类MultithreadEventExcutorGroup的构造方法创建事件执行...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 161,314
精华内容 64,525
关键字:

一次性解析源码