精华内容
下载资源
问答
  • 源码阅读|年轻人可以不讲武德,但是你得会阅读Naocs源码

    为什么我会经常阅读源码呢,因为阅读源码能让你更加接近大佬,哈哈,这是我瞎扯的。

    这篇文章将会带大家阅读Nacos源码 以及 教大家阅读源码的技巧,我们正式开始吧!

    先给大家献上一张我梳理的高清源码图,方便大家对nacos的源码有一个整体上的认识。

    在这里插入图片描述

    有了这张图,我们就很容易去看nacos源码了。

    如何找切入点

    首先我们得要找一个切入点进入到nacos源码中,那么就从nacos依赖入手

       <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
      </dependency>
    

    进入这个依赖文件,会发现它又依赖了一个组件:

    <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-nacos-discovery</artifactId>
    </dependency>
    

    进入依赖之后,我们发现它长这样:

    在这里插入图片描述

    从这张图中,我们发现了一个熟悉的配置文件spring.factories,这是sringboot自动装配的必备文件

    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
      com.alibaba.cloud.nacos.discovery.NacosDiscoveryAutoConfiguration,\
      com.alibaba.cloud.nacos.ribbon.RibbonNacosAutoConfiguration,\
      com.alibaba.cloud.nacos.endpoint.NacosDiscoveryEndpointAutoConfiguration,\
      com.alibaba.cloud.nacos.registry.NacosServiceRegistryAutoConfiguration,\
      com.alibaba.cloud.nacos.discovery.NacosDiscoveryClientConfiguration,\
      com.alibaba.cloud.nacos.discovery.reactive.NacosReactiveDiscoveryClientConfiguration,\
      com.alibaba.cloud.nacos.discovery.configclient.NacosConfigServerAutoConfiguration
    org.springframework.cloud.bootstrap.BootstrapConfiguration=\
      com.alibaba.cloud.nacos.discovery.configclient.NacosDiscoveryClientConfigServiceBootstrapConfiguration
    

    因为这张主要说的是服务注册源码,所以我们可以只用关注(NacosServiceRegistryAutoConfiguration)自动装配文件

    public class NacosServiceRegistryAutoConfiguration {
    
    	@Bean
    	public NacosServiceRegistry nacosServiceRegistry(
    			NacosDiscoveryProperties nacosDiscoveryProperties) {
    		return new NacosServiceRegistry(nacosDiscoveryProperties);
    	}
    
    	@Bean
    	@ConditionalOnBean(AutoServiceRegistrationProperties.class)
    	public NacosRegistration nacosRegistration(
    			NacosDiscoveryProperties nacosDiscoveryProperties,
    			ApplicationContext context) {
    		return new NacosRegistration(nacosDiscoveryProperties, context);
    	}
    
    	@Bean
    	@ConditionalOnBean(AutoServiceRegistrationProperties.class)
    	public NacosAutoServiceRegistration nacosAutoServiceRegistration(
    			NacosServiceRegistry registry,
    			AutoServiceRegistrationProperties autoServiceRegistrationProperties,
    			NacosRegistration registration) {
    		return new NacosAutoServiceRegistration(registry,
    				autoServiceRegistrationProperties, registration);
    	}
    
    }
    

    我们看到的是三个bean注入,这里给大家介绍一个看源码的小技巧:自动装配的文件中申明的bean类,我们只需要看带有auto的bean,这个往往是入口;NacosAutoServiceRegistration 带有auto,我们点进去看看里面都有什么:

    	@Override
    	protected void register() {
    		if (!this.registration.getNacosDiscoveryProperties().isRegisterEnabled()) {
    			log.debug("Registration disabled.");
    			return;
    		}
    		if (this.registration.getPort() < 0) {
    			this.registration.setPort(getPort().get());
    		}
    		super.register();
    	}
    

    里面有一个register()方法,我在这里打个断点,因为我猜测这个就是注册的入口,我现在使用debug模式,启动一个服务,看它会不会调用这个方法:

    客户端注册

    这里贴上我debug后,进入register方法的调用链截图

    在这里插入图片描述

    看到这个调用链,看到一个onApplicationEvent的回调方法,找到这个方法所在的类AbstractAutoServiceRegistration
    这个类继承了ApplicationListener这个多播器监听器,spring启动之后,会发布多播器事件,然后回调实现多播器组件的onApplicationEvent方法,我们从这个方法开始分析:

    public void onApplicationEvent(WebServerInitializedEvent event) {
    		bind(event); // 绑定端口,并启动
    	}
    	
    		@Deprecated
    public void bind(WebServerInitializedEvent event) {
    // 设置端口
        this.port.compareAndSet(0, event.getWebServer().getPort());
        // 启动客户端注册组件
    	this.start();
    }
    public void start() {
            // 省略分支代码
            // 调用注册
    			register();
    	}
    

    因为springcloud提供了多种注册中心扩展,但是我们这里只引用了nacos注册中心,所以这里直接调用的是NacosServiceRegistry的register方法:

    	public void register(Registration registration) {
    
        // 省略分支代码
        // 获取服务id
    		String serviceId = registration.getServiceId();
    		// 获取组配置
    		String group = nacosDiscoveryProperties.getGroup();
         // 封装服务实例
    		Instance instance = getNacosInstanceFromRegistration(registration);
    		// 调用 命名服务的 registerInstance方法 注册实例
    			namingService.registerInstance(serviceId, group, instance);
    	}
    

    进入到registerInstance方法

        public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException {
            if (instance.isEphemeral()) {
                // 省略分支代码
                // 与服务端建立心跳,默认每隔5秒定时发送新跳包
                this.beatReactor.addBeatInfo(NamingUtils.getGroupedName(serviceName, groupName), beatInfo);
            }
            // 通过http方式向服务端发送注册请求
            this.serverProxy.registerService(NamingUtils.getGroupedName(serviceName, groupName), groupName, instance);
        }
    

    serverproxy通过调用对http进行封装的reapi方法,向服务端接口("/nacos/v1/ns/instance")发送请求,

       public void registerService(String serviceName, String groupName, Instance instance) throws NacosException {
            LogUtils.NAMING_LOGGER.info("[REGISTER-SERVICE] {} registering service {} with instance: {}", new Object[]{this.namespaceId, serviceName, instance});
            Map<String, String> params = new HashMap(9);
            params.put("namespaceId", this.namespaceId);
            params.put("serviceName", serviceName);
            params.put("groupName", groupName);
            params.put("clusterName", instance.getClusterName());
            params.put("ip", instance.getIp());
            params.put("port", String.valueOf(instance.getPort()));
            params.put("weight", String.valueOf(instance.getWeight()));
            params.put("enable", String.valueOf(instance.isEnabled()));
            params.put("healthy", String.valueOf(instance.isHealthy()));
            params.put("ephemeral", String.valueOf(instance.isEphemeral()));
            params.put("metadata", JSON.toJSONString(instance.getMetadata()));
            this.reqAPI(UtilAndComs.NACOS_URL_INSTANCE, params, (String)"POST");
        }
    

    我们知道nacos经常是以集群形式部署的,那客户端是如何选择其中一个节点发送呢,肯定得实现负载均衡的逻辑,我们点击reqAPI,看它是如何实现的

     if (servers != null && !servers.isEmpty()) {
                    Random random = new Random(System.currentTimeMillis());
                    // 随机获取一个索引,servers保存的是所有nacos节点地址
                    int index = random.nextInt(servers.size());
                    // 遍历所有节点,根据index值,从servers中找到对应位置的server,进行请求调用,如果调用成功则返回,否则依次往后遍历,直到请求成功
                    for(int i = 0; i < servers.size(); ++i) {
                        String server = (String)servers.get(index);
    
                        try {
                            return this.callServer(api, params, server, method);
                        } catch (NacosException var11) {
                            exception = var11;
                            LogUtils.NAMING_LOGGER.error("request {} failed.", server, var11);
                        } catch (Exception var12) {
                            exception = var12;
                            LogUtils.NAMING_LOGGER.error("request {} failed.", server, var12);
                        }
                        // index+1 然后取模 是保证index不会越界
                        index = (index + 1) % servers.size();
                    }
    
                    throw new IllegalStateException("failed to req API:" + api + " after all servers(" + servers + ") tried: " + ((Exception)exception).getMessage());
                }
    

    到这里,客户端注册的代码已经分析完了,不过这还不是本篇的结束,我们还得继续分析服务端是如何处理客户端发送过来的注册请求:

    服务端处理客户端注册请求

    如果需要查看服务端源码的话,则需要将nacos源码下下来 下载地址

    我们从服务注册api接口地址(/nacos/v1/ns/instance),可以找到对应的controller为(com.alibaba.nacos.naming.controllers.InstanceController)

    因为注册实例发送的是post请求,所以直接找被postmapping注解的register方法

     @CanDistro
        @PostMapping
        public String register(HttpServletRequest request) throws Exception {
    // 获取服务名
            String serviceName = WebUtils.required(request, CommonParams.SERVICE_NAME);
    // 获取命名空间id
            String namespaceId = WebUtils.optional(request, CommonParams.NAMESPACE_ID, Constants.DEFAULT_NAMESPACE_ID);
    
    // 注册实例
    serviceManager.registerInstance(namespaceId, serviceName, parseInstance(request));
            return "ok";
        }
    

    我们点击进入到registerInstance方法:

        public void registerInstance(String namespaceId, String serviceName, Instance instance) throws NacosException {
    
            createEmptyService(namespaceId, serviceName, instance.isEphemeral());
    
            Service service = getService(namespaceId, serviceName);
    
            if (service == null) {
                throw new NacosException(NacosException.INVALID_PARAM,
                    "service not found, namespace: " + namespaceId + ", service: " + serviceName);
            }
    // 执行添加实例的操作
            addInstance(namespaceId, serviceName, instance.isEphemeral(), instance);
        }
    

    分析

    在nacos中,注册实例后,还需要将注册信息同步到其他节点,所有在nacos中存在两种同步模式AP和CP,ap和cp主要体现在集群中如何同步注册信息到其它集群节点的实现方式上;
    nacos通过ephemeral 字段值来决定是使用ap方式同步还是cp方式同步,默认使用的的ap方式同步注册信息。
    com.alibaba.nacos.naming.core.ServiceManager.addInstance()

        public void addInstance(String namespaceId, String serviceName, boolean ephemeral, Instance... ips) throws NacosException {
            // 生成服务的key
            String key = KeyBuilder.buildInstanceListKey(namespaceId, serviceName, ephemeral);
            // 获取服务
            Service service = getService(namespaceId, serviceName);
            // 使用同步锁处理
            synchronized (service) {
                List<Instance> instanceList = addIpAddresses(service, ephemeral, ips);
    
                Instances instances = new Instances();
                instances.setInstanceList(instanceList);
                // 调用consistencyService.put 处理同步过来的服务
                consistencyService.put(key, instances);
            }
        }
    

    我们在进入到consistencyService.put方法中

    在这里插入图片描述

    点击put方法时,会看到有三个实现类,根据上下文(或者debug方式),可以推断出这里引用的是DelegateConsistencyServiceImpl实现类

        @Override
        public void put(String key, Record value) throws NacosException {
            // 进入到这个put方法后,就可以知道应该使用ap方式同步还是cp方式同步
            mapConsistencyService(key).put(key, value);
        }
    

    从下面的方法中 可以判断通过key来判断使用ap还是cp来同步注册信息,其中key是由ephemeral字段组成;

       private ConsistencyService mapConsistencyService(String key) {
            return KeyBuilder.matchEphemeralKey(key) ? ephemeralConsistencyService : persistentConsistencyService;
        }
    

    AP 方式同步的流程(ephemeralConsistencyService)

    本地服务器处理注册信息&将注册信息同步到其它节点

        @Override
        public void put(String key, Record value) throws NacosException {
            // 处理本地注册列表
            onPut(key, value);
            // 添加阻塞任务,同步信息到其他集群节点
            taskDispatcher.addTask(key);
        }
    

    处理本地注册节点

    nacos将key做为一个task,添加到notifer中阻塞队列tasks中,并且使用单线程执行,其中notifer是初始化的时候,作为一个线程被放到线程池中(线程池只设置了一个核心线程);

    这里有一个点需要告诉大家:在大多数分布式框架,都会采用单线程的阻塞队列来处理耗时的任务,一方面解决并发问题,另一方面能够解决并发带来的写写冲突问题。

    线程中的主要处理逻辑就是,循环读取阻塞队列中的内容,然后处理注册信息,更新到内存注册列表中。

    同步注册信息到其他集群节点

    nacos同样也是把注册key作为一个task存放到 TaskDispatcher 中的taskShedule阻塞队列中,然后开启线程循环读取阻塞队列:

           @Override
            public void run() {
    
                List<String> keys = new ArrayList<>();
                while (true) {
                        String key = queue.poll(partitionConfig.getTaskDispatchPeriod(),
                            TimeUnit.MILLISECONDS);
                        // 省略判断代码
                        // 添加同步的key
                        keys.add(key);
                        // 计数
                        dataSize++;
                        // 判断同步的key大小是否等于 批量同步设置的限量 或者 判断据上次同步时间 是否大于 配置的间隔周期,如果满足任意一个,则开始同步
                        if (dataSize == partitionConfig.getBatchSyncKeyCount() ||
                            (System.currentTimeMillis() - lastDispatchTime) > partitionConfig.getTaskDispatchPeriod()) {
                            // 遍历所有集群节点,直接调用http进行同步
                            for (Server member : dataSyncer.getServers()) {
                                if (NetUtils.localServer().equals(member.getKey())) {
                                    continue;
                                }
                                SyncTask syncTask = new SyncTask();
                                syncTask.setKeys(keys);
                                syncTask.setTargetServer(member.getKey());
    
                                if (Loggers.DISTRO.isDebugEnabled() && StringUtils.isNotBlank(key)) {
                                    Loggers.DISTRO.debug("add sync task: {}", JSON.toJSONString(syncTask));
                                }
    
                                dataSyncer.submit(syncTask, 0);
                            }
                            // 记录本次同步时间
                            lastDispatchTime = System.currentTimeMillis();
                            // 计数清零
                            dataSize = 0;
                        }
                }
            }
        }
    

    使用ap方式作同步的过程很简单,但是这里面有两种设计思路来解决单个key同步的问题:
    如果有新的key推送上来,nacos就发起一次同步,这会造成网络资源浪费,因为每次同步的就只有一个key或者几个key;

    同步少量的key解决方案:
    1. 只有积累到指定数量的key,才发起批量同步
    2. 距离上次同步时间超过配置的限制时间,则忽略key数量,直接发起同步

    CP 方式同步的流程(RaftConsistencyServiceImpl)

    cp模式追求的是数据一致性,为了数据一致性,那么肯定得选出一个leader,由leader首先同步,然后再由leader通知follower前来获取最新的注册节点(或者主动推送给follower)

    nacos使用raft协议来进行选举leader,来实现cp模式。

    同样进入到 RaftConsistencyServiceImpl的put方法

        @Override
        public void put(String key, Record value) throws NacosException {
            try {
                raftCore.signalPublish(key, value);
            } catch (Exception e) {
                Loggers.RAFT.error("Raft put failed.", e);
                throw new NacosException(NacosException.SERVER_ERROR, "Raft put failed, key:" + key + ", value:" + value, e);
            }
        }
    

    进入到raftCore.signalPublish方法中,我提取几个关键的代码

    // 首先判断当前nacos节点是否是leader,如果不是leader,则获取leader节点的ip,然后将请求转发到leader处理,否则往下走
    if (!isLeader()) {
                JSONObject params = new JSONObject();
                params.put("key", key);
                params.put("value", value);
                Map<String, String> parameters = new HashMap<>(1);
                parameters.put("key", key);
    
                raftProxy.proxyPostLarge(getLeader().ip, API_PUB, params.toJSONString(), parameters);
                return;
            }
    

    同样采用同样队列的方式,去处理本地注册列表

    onPublish(datum, peers.local());
    
    public void onPublish(Datum datum, RaftPeer source) throws Exception {
           
            // 添加同步key任务到阻塞队列中
            notifier.addTask(datum.key, ApplyAction.CHANGE);
    
            Loggers.RAFT.info("data added/updated, key={}, term={}", datum.key, local.term);
        }
    

    遍历所有集群节点,发送http同步请求

     for (final String server : peers.allServersIncludeMyself()) {
                    // 如果是leader,则不进行同步
                    if (isLeader(server)) {
                        latch.countDown();
                        continue;
                    }
                    // 组装url 发送同步请求到其它集群节点
                    final String url = buildURL(server, API_ON_PUB);
                    HttpClient.asyncHttpPostLarge(url, Arrays.asList("key=" + key), content, new AsyncCompletionHandler<Integer>() {
                        @Override
                        public Integer onCompleted(Response response) throws Exception {
                            if (response.getStatusCode() != HttpURLConnection.HTTP_OK) {
                                Loggers.RAFT.warn("[RAFT] failed to publish data to peer, datumId={}, peer={}, http code={}",
                                    datum.key, server, response.getStatusCode());
                                return 1;
                            }
                            latch.countDown();
                            return 0;
                        }
    
                        @Override
                        public STATE onContentWriteCompleted() {
                            return STATE.CONTINUE;
                        }
                    });
    
                }
    

    到此,nacos服务注册及服务实例同步的主干源码已经分析完了。

    总结

    对于刚开始接触nacos源码的同学,可以先把头上的图多看几遍,然后对照着源码找到对应的位置 ,最后结合图再结合本文,整体连贯的看下来,相信会有很大收获的;虽然阅读源码的过程很痛苦,但是你只要坚持下来了,掌握到了阅读源码的技巧,你就会发现再难的源码,你都能把它啃下来;后面我会专门写一篇教你如何高效阅读源码的文章,希望对于刚接触源码的同学能有所帮助。

    微信搜一搜【AI码师】关注帅气的我,回复【干货领取】,将会有大量面试资料和架构师必看书籍等你挑选,包括java基础、java并发、微服务、中间件等更多资料等你来取哦。

    展开全文
  • JDK源码阅读顺序

    万次阅读 多人点赞 2018-03-15 17:42:23
    很多java开发的小伙伴都会阅读jdk源码,然而确不知道应该从哪读起。以下为小编整理的通常所需阅读源码范围。 标题为包名,后面序号为优先级1-4,优先级递减 1、java.lang 1) Object 1 2) String 1 3) ...

    很多java开发的小伙伴都会阅读jdk源码,然而确不知道应该从哪读起。以下为小编整理的通常所需阅读的源码范围。
    标题为包名,后面序号为优先级1-4,优先级递减
    1、java.lang

    1) Object 1
    2) String 1
    3) AbstractStringBuilder 1
    4) StringBuffer 1
    5) StringBuilder 1
    6) Boolean 2
    7) Byte 2
    8) Double 2
    9) Float 2
    10) Integer 2
    11) Long 2
    12) Short 2
    13) Thread 2
    14) ThreadLocal 2
    15) Enum 3
    16) Throwable 3
    17) Error 3
    18) Exception 3
    19) Class 4
    20) ClassLoader 4
    21) Compiler 4
    22) System 4
    23) Package 4
    24) Void 4
    

    2、java.util

    1) AbstractList 1
    2) AbstractMap 1
    3) AbstractSet 1
    4) ArrayList 1
    5) LinkedList 1
    6) HashMap 1
    7) Hashtable 1
    8) HashSet 1
    9) LinkedHashMap 1
    10) LinkedHashSet 1
    11) TreeMap 1
    12) TreeSet 1
    13) Vector 2
    14) Queue 2
    15) Stack 2
    16) SortedMap 2
    17) SortedSet 2
    18) Collections 3
    19) Arrays 3
    20) Comparator 3
    21) Iterator 3
    22) Base64 4
    23) Date 4
    24) EventListener 4
    25) Random 4
    26) SubList 4
    27) Timer 4
    28) UUID 4
    29) WeakHashMap 4
    

    3、java.util.concurrent

    1) ConcurrentHashMap 1
    2) Executor 2
    3) AbstractExecutorService 2
    4) ExecutorService 2
    5) ThreadPoolExecutor 2
    6) BlockingQueue 2
    7)AbstractQueuedSynchronizer 2
    8)CountDownLatch 2
    9) FutureTask 2
    10)Semaphore 2
    11)CyclicBarrier 2
    13)CopyOnWriteArrayList 3
    14)SynchronousQueue 3
    15)BlockingDeque 3
    16) Callable 4
    

    4、java.util.concurrent.atomic

    1) AtomicBoolean 2
    2) AtomicInteger 2
    3) AtomicLong 2
    4) AtomicReference 3
    

    5、java.lang.reflect

    1) Field 2
    2) Method 2
    

    6、java.lang.annotation

    1) Annotation 3
    2) Target 3
    3) Inherited 3
    4) Retention 3
    5) Documented 4
    6) ElementType 4
    7) Native 4
    8) Repeatable 4
    

    7、java.util.concurrent.locks

    1) Lock 2
    2) Condition 2
    3) ReentrantLock 2
    4) ReentrantReadWriteLock 2
    

    8、java.io

    1) File 3
    2) InputStream   3
    3) OutputStream  3
    4) Reader  4
    5) Writer  4
    

    9、java.nio

    1) Buffer 3
    2) ByteBuffer 4
    3) CharBuffer 4
    4) DoubleBuffer 4
    5) FloatBuffer 4
    6) IntBuffer 4
    7) LongBuffer 4
    8) ShortBuffer 4
    

    10、java.sql

    1) Connection 3
    2) Driver 3
    3) DriverManager 3
    4) JDBCType 3
    5) ResultSet 4
    6) Statement 4
    

    11、java.net

    1) Socket 3
    2) ServerSocket 3
    3) URI 4
    4) URL 4
    5) URLEncoder 4
    

    阅读笔记简版

    1、ArrayList

    1)Object[] elementData:数据存储
    2)int size:使用数量
    3)int modCount:操作次数
    4)初始化:
      a、指定容量初始化数组;
      b、不指定容量第一次add数据时初始化数组容量10
    5)扩容:
      a、1.5倍;
      b、不够取所需最小;
      c、新容量大于MAX_ARRAY_SIZE(Integer.MAX_VALUE-8),按所需容量取MAX_ARRAY_SIZE和Integer.MAX_VALUE较小值
    

    2、LinkedList

    1) Node {E item, Node prev, Node next}
    2) int size
    3) Node first
    4) Node last
    5) linkFirst(), linkLast(), linkBefore(), unLinkFirst(), unLinkLast(), unLink(), indexOf()
    6)int modCount
    

    3、HashMap

    1) Node{int hash, K key, V value, Node next}
    注:hash是根据key算的
    2) Node[] table:数据存储,默认大小16
    3) Set<Map.Entry> entrySet:用于Map遍历的集合
    4) int size:当前数量
    5) int threshold:size超过多少时需要扩容,默认16
    6) float loadFactor:负载因子,默认0.75f
    7)int modCount:操作次数
    8) put():根据key算hash,根据容量和hash算index,table[index]没有直接添加到数组中,table[index]有,若index位置同一个key则更新,否则遍历next是否有,有则更新,无则新增
    注:判断key是否相等,先比较hash,若相等在比较equals
    9)扩容:put后,当size>threshold时需要扩容,扩容时容量翻倍,重新算hash复制到新数组
    10)哈希冲突:1.7以前数组+链表,1.8开始数组+红黑树
    11)get()类似
    

    4、ConcurrentHashMap

    1) JDK1.7及以前:
    	a、Segment[] ,HashEntry[] , HashEntry{hash, k, v, next}
    	b、根据key算hash,根据hash和Segment的大小算位置,每个segment拥有一个自己的HashEntry[]
    	c、get():不加锁,volatile类型
    	d、put(): 对相应segment加锁
    	e、size():各HashEntry[] 之和,先不加锁算两遍,若一致则返回,若不一致则加锁重新计算
    2)JDK1.8
    	a、Node{hash, key, value, next}
    	b、Node[] table
    	c、大多数操作类似于HashMap,根据key算hash,在根据hash和容量算index,对table[index]加锁,从而达到更大的并发量
    	d、get(): 同HashMap
    	e、put(): 对table[index]加锁,如果table[index]为null则使用CAS操作,如果不为null对table[index]加synchronized
    

    5、Hashtable

    1) 结构实现与HashMap基本一致
    2)通过synchronized方法保证线程安全
    

    6、LinkedHashMap

    1)继承HashMap
    2) Entry{HashMap.Node, Entry before, after}
    3) Entry head, tail
    4) 重写newNode()添加节点时,除像HashMap中添加外,保存before、after信息
    

    7、TreeMap

    1)红黑树,即自平衡二叉查找树,时间复杂度O(logn)
    2)Entry{K k, V v, Entry parent, left, right, boolean color}
    3)Entry root,int size, int modeCount
    

    8、Object

    1) wait(), notify(), notifyAll(), wait(timeout)
    2) hashCode(), equals()
    3) clone()
    

    9、String

    1) final char[] value
    2) int hash
    3) equals(), startWith(), endWith(), replace
    

    10、AbstractStringBuilder

    1) char[] value
    2) int count
    3) 扩容:翻倍,不够取所需最小
    

    11、StringBuilder:继承AbstractStringBuilder
    12、StringBuffer

    1) 继承AbstractStringBuilder
    2) synchronized方法保证线程安全
    3) char[] toStringCache
    

    13、Set一般都是使用委托模式到Map
    14、AbstractMap维护EntrySet,AbstractSet维护Iterator,AbstractList维护Iterator

    展开全文
  • LinkedList源码阅读

    千次阅读 2018-09-27 15:02:49
    LinkedList源码阅读

    主要内容:

    1.  LinkedList是双向还是单向?
    2. 为什么说LinkedList不支持高效的随机元素访问?
        /**
         * 创建一个空列表
         */
        public LinkedList() {
        }
    
        // LinkedList内部会维护头尾节点,所以getFirst()、getLast()都是很快的。
        transient int size = 0;
        transient Node<E> first;
        transient Node<E> last;

    底层是一个Node内部类构成的链表:

        private static class Node<E> {
            E item;
            Node<E> next;
            Node<E> prev;
    
            Node(Node<E> prev, E element, Node<E> next) {
                this.item = element;
                this.next = next;
                this.prev = prev;
            }
        }

    linkFirst

        /**
         * 链接e作为第一个元素。
         */
        private void linkFirst(E e) {
            // 如果当前链表刚刚初始化即为空,则f为null
            final Node<E> f = first;
            // 创建一个新节点,排列顺序为null,e,f
            final Node<E> newNode = new Node<>(null, e, f);
            // 将新节点赋值给首节点
            first = newNode;
            // 如果链表为空
            if (f == null)
                last = newNode;
            else
                // 此时f已经不再是首节点
                f.prev = newNode;
            // 增加一个元素后,size+1
            size++;
            modCount++;
        }

     

    linkLast

        /**
         * 链接e作为最后一个元素。
         */
        void linkLast(E e) {
            // 如果当前链表刚刚初始化即为空,则l为null
            final Node<E> l = last;
            // 创建一个新节点,排列顺序为l,e,null
            final Node<E> newNode = new Node<>(l, e, null);
            // 将新节点赋值给尾节点
            last = newNode;
            // 如果尾节点为空,即链表为空
            if (l == null)
                first = newNode;
            else
                l.next = newNode;
            size++;
            modCount++;
        }

    get

        public E get(int index) {
            checkElementIndex(index);
            return node(index).item;
        }
    
        /**
         * 返回指定元素索引处的(非空)节点。
         * LinkedList#get(index)(较慢,因为要通过node(index)方法遍历到i元素再取出,
         * 而链表是不连续的)
         */
        Node<E> node(int index) {
            // 比较索引值和链表长度的1/2
            if (index < (size >> 1)) {
            // 如果后者大,就从首节点一个个往后找
                Node<E> x = first;
                for (int i = 0; i < index; i++)
                    // 一个个的next本质是顺序访问
                    x = x.next;
                return x;
            } else {
                // 如果前者大,就从尾结点一个个往前找
                Node<E> x = last;
                for (int i = size - 1; i > index; i--)
                    x = x.prev;
                return x;
            }
        }

    linkBefore

        /**
         * 在指定索引处插入元素e
         */
        public void add(int index, E element) {
            checkPositionIndex(index);
    
            if (index == size)
                linkLast(element);
            else
                linkBefore(element, node(index));
        }
        /**
         * 在非空的节点succ前插入e节点
         */
        void linkBefore(E e, Node<E> succ) {
            // assert succ != null;
            final Node<E> pred = succ.prev;
            final Node<E> newNode = new Node<>(pred, e, succ);
            succ.prev = newNode;
            if (pred == null)
                first = newNode;
            else
                pred.next = newNode;
            size++;
            modCount++;
        }

    最后说一点:虽然我们分析问题时都是强调链表结构插入和删除比数组结构快,但理想化的链表节点插入和删除是不存在的,任何基于线性结构的容器,插入和删除的实现必然伴随着遍历。虽然链表对于某个节点的插入和删除确实比数组快,但是遍历相对较吃力,所以实际增删的效率并不能一概而论。

    展开全文
  • nacos源码阅读--服务注册

    万次阅读 2021-02-07 18:16:01
    NacosServiceRegistry可以通过... } } } 2.1 getServiceId查看源码最终发现读取了配置文件的spring项目的name 2.2 getNacosInstanceFromRegistration方法获取了本地客户端的一些信息,然后生成实例 private Instance ...

    一、springboot启动日志报NacosServiceRegistry

    springboot+nacos启动时,log会有一条nacos注册信息如下:
    在这里插入图片描述
    **很显然,启动注册服务的操作便在NacosServiceRegistry当中。

    NacosServiceRegistry可以通过查找所有类直接寻找,也可以从以下springboot自动装配的角度去寻找

    通过查看spring.factories文件也可以看到,nacos自动装配的类如下,nacos按照了springboot的约定进行了自动装配:**
    在这里插入图片描述
    进入到NacosDiscoveryAutoConfiguration发现,前两个Bean都是作为最后一个Bean的参数,猜想第三个bean是主要的注册方法。
    在这里插入图片描述
    ****点进去发现他继承了AbstractAutoServiceRegistration.class,并且AbstractAutoServiceRegistration.class实现了ApplicationContextAware以及ApplicationListener,果然下方有一个onApplicationEvent的方法
    在这里插入图片描述
    监听器中有一个bind方法,bind方法中又有一个start方法:

    public void start() {
            if (!this.isEnabled()) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Discovery Lifecycle disabled. Not starting");
                }
    
            } else {
                if (!this.running.get()) {
                    this.context.publishEvent(new InstancePreRegisteredEvent(this, this.getRegistration()));
                    this.register();
                    if (this.shouldRegisterManagement()) {
                        this.registerManagement();
                    }
    
                    this.context.publishEvent(new InstanceRegisteredEvent(this, this.getConfiguration()));
                    this.running.compareAndSet(false, true);
                }
    
            }
        }
    

    其中的register便是注册方法。由此便找到了NacosServiceRegistry.class

    二、register剖析

    register方法如下:

    public void register(Registration registration) {
            if (StringUtils.isEmpty(registration.getServiceId())) {
                log.warn("No service to register for nacos client...");
            } else {
                String serviceId = registration.getServiceId();
                Instance instance = this.getNacosInstanceFromRegistration(registration);
    
                try {
                    this.namingService.registerInstance(serviceId, instance);
                    log.info("nacos registry, {} {}:{} register finished", new Object[]{serviceId, instance.getIp(), instance.getPort()});
                } catch (Exception var5) {
                    log.error("nacos registry, {} register failed...{},", new Object[]{serviceId, registration.toString(), var5});
                }
    
            }
        }
    

    2.1 getServiceId查看源码最终发现读取了配置文件的spring项目的name
    在这里插入图片描述
    2.2 getNacosInstanceFromRegistration方法获取了本地客户端的一些信息,然后生成实例

     private Instance getNacosInstanceFromRegistration(Registration registration) {
            Instance instance = new Instance();
            instance.setIp(registration.getHost());
            instance.setPort(registration.getPort());
            instance.setWeight((double)this.nacosDiscoveryProperties.getWeight());
            instance.setClusterName(this.nacosDiscoveryProperties.getClusterName());
            instance.setMetadata(registration.getMetadata());
            return instance;
        }
    

    那么注册的主要方法便是this.namingService.registerInstance(serviceId, instance);

    2.3 registerInstance方法设置了注册需要的一些基础信息,其中groupname默认为DEFAULT_GROUP

    public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException {
            if (instance.isEphemeral()) {
                BeatInfo beatInfo = new BeatInfo();
                beatInfo.setServiceName(NamingUtils.getGroupedName(serviceName, groupName));
                beatInfo.setIp(instance.getIp());
                beatInfo.setPort(instance.getPort());
                beatInfo.setCluster(instance.getClusterName());
                beatInfo.setWeight(instance.getWeight());
                beatInfo.setMetadata(instance.getMetadata());
                beatInfo.setScheduled(false);
                long instanceInterval = instance.getInstanceHeartBeatInterval();
                beatInfo.setPeriod(instanceInterval == 0L ? DEFAULT_HEART_BEAT_INTERVAL : instanceInterval);
                this.beatReactor.addBeatInfo(NamingUtils.getGroupedName(serviceName, groupName), beatInfo);
            }
    
            this.serverProxy.registerService(NamingUtils.getGroupedName(serviceName, groupName), groupName, instance);
        }
    

    其中最后一行代码执行了注册动作,注册之前会通过schedule的方式添加定时心跳检测任务,默认五秒检测,而且会根据计算机可用资源核心数除以二分配检测资源(最小为1核心数),暂时不看

    2.4 registerService方法进行了post请求,将本地的一些基础信息作为参数放入到请求体当中,进行post

    public void registerService(String serviceName, String groupName, Instance instance) throws NacosException {
            LogUtils.NAMING_LOGGER.info("[REGISTER-SERVICE] {} registering service {} with instance: {}", new Object[]{this.namespaceId, serviceName, instance});
            Map<String, String> params = new HashMap(9);
            params.put("namespaceId", this.namespaceId);
            params.put("serviceName", serviceName);
            params.put("groupName", groupName);
            params.put("clusterName", instance.getClusterName());
            params.put("ip", instance.getIp());
            params.put("port", String.valueOf(instance.getPort()));
            params.put("weight", String.valueOf(instance.getWeight()));
            params.put("enable", String.valueOf(instance.isEnabled()));
            params.put("healthy", String.valueOf(instance.isHealthy()));
            params.put("ephemeral", String.valueOf(instance.isEphemeral()));
            params.put("metadata", JSON.toJSONString(instance.getMetadata()));
            this.reqAPI(UtilAndComs.NACOS_URL_INSTANCE, params, (String)"POST");
        }
    

    最后通过callServer方法中的httpClient进行了http请求,请求地址为http://127.0.0.1:8848/nacos/v1/ns/instance
    在这里插入图片描述
    而通过debug执行这一行代码之后,在nacos就能看到服务已经注册了上去。

    客户端已经知道注册的流程是怎么样的了,接下来看服务端接收到http请求会有什么样的操作。

    三、服务端的处理

    上面的http请求地址为http://127.0.0.1:8848/nacos/v1/ns/instance,对应的controller方法为register方法,位置如下:
    在这里插入图片描述
    3.1 点击去之后发现最终的实现方法为registerInstance

    public void registerInstance(String namespaceId, String serviceName, Instance instance) throws NacosException {
            
            createEmptyService(namespaceId, serviceName, instance.isEphemeral());
            
            Service service = getService(namespaceId, serviceName);
            
            if (service == null) {
                throw new NacosException(NacosException.INVALID_PARAM,
                        "service not found, namespace: " + namespaceId + ", service: " + serviceName);
            }
            
            addInstance(namespaceId, serviceName, instance.isEphemeral(), instance);
        }
    

    首先会去创建一个新的service,namespaceId为键,如果namespaceId不存在,则执行createServiceIfAbsent,服务第一次都会进行这个方法的执行,执行之后会保存在一个叫serviceMap的Map存储结构当中,相当于缓存

    public void createEmptyService(String namespaceId, String serviceName, boolean local) throws NacosException {
        createServiceIfAbsent(namespaceId, serviceName, local, null);
    }
    

    3.2 接着是addInstance方法,这个方法会根据ip地址

    public void addInstance(String namespaceId, String serviceName, boolean ephemeral, Instance... ips)
                throws NacosException {
            
            String key = KeyBuilder.buildInstanceListKey(namespaceId, serviceName, ephemeral);
            
            Service service = getService(namespaceId, serviceName);
            
            synchronized (service) {
                List<Instance> instanceList = addIpAddresses(service, ephemeral, ips);
                
                Instances instances = new Instances();
                instances.setInstanceList(instanceList);
                
                consistencyService.put(key, instances);
            }
        }
    

    最后通过consistencyService.put(key, instances);做持久化,并没有持久到数据库当中。

    ConsistencyService类会通过添加监听器的方式,当值进行变化时执行onchange方法来更改服务当前状态:

    展开全文
  • MyBatis源码阅读

    2018-09-30 08:21:09
    mybatis源码阅读
  • 再谈源码阅读

    2019-07-21 09:36:59
    再谈源码阅读
  • nacos源码阅读--本地单机debug调试

    万次阅读 2021-02-07 17:49:25
    源码长这个怂样子: 二、找到distribution中的sql脚本,运行sql脚本 可以复制到navicat或其他数据库管理软件中执行 三、找到console中如下的配置文件: 里面有几行配置代码,取消注释,然后改成自己本地的配置: ...
  • Redis源码阅读

    万次阅读 2019-03-18 16:00:04
    Redis源码阅读 文章目录Redis源码阅读1 数据结构1.1 动态字符串SDS1.2 双向链表ADLIST1.3 字典 DICT1.4 跳跃表1.5 hyperloglog2 内存编码2.1 整数集合intset2.2 压缩列表ziplist3 数据类型的实现3.1 Object4 数据库...
  • afl源码阅读

    2020-04-09 18:40:22
    afl源码阅读(原始策略): https://blog.csdn.net/weixin_30500105/article/details/97935796
  • nacos源码阅读--服务发现

    万次阅读 2021-03-07 01:13:41
    一、客户端使用FeignClient远端请求 1.1 一个最简单的请求接口: @Service @FeignClient("provider") public interface Feign { @GetMapping("hello") String hello(); } ...1.2 为了方便测试,在controller里直接...
  • tensorflow源码阅读

    2018-07-03 19:52:33
    title: tensorflow源码阅读 categories: tensorflow tags: 源码 http://www.cnblogs.com/yao62995/p/5773578.html
  • 源码阅读工具 UnderStand

    千次阅读 2019-12-18 19:16:29
    源码阅读工具 UnderStand 特色: 1、支持多语言:Ada, C, C++, C#, Java, FORTRAN, Delphi, Jovial, and PL/M ,混合语言的project也支持 2、多平台:Windows/Linux/Solaris/HP-UX/IRIX/MAC OS X 3、代码语法高亮、...
  • kafka源码阅读环境搭建

    千次阅读 2019-08-22 00:47:33
    源码阅读环境搭建
  • Linux内核源码阅读以及工具(转)

    万次阅读 2018-09-21 01:04:33
    Linux内核源码阅读以及工具(转) 转载地址:Linux内核源码阅读以及工具(转)
  • Hive源码阅读之路

    千次阅读 多人点赞 2020-11-08 14:12:43
    Hive源码阅读(1)阅读环境搭建前言:让学习成为一种习惯环境准备Hive源码下载Hive源码目录hive三个最重要的组件:其他组件hive辅助组件编译源码配置Hive本地调试配置IDEA-DEBUG(注意端口和Host)开始Debug,配置...
  • Enterprise Architect https://blog.csdn.net/WHHGARSKY/article/details/70164180 Source Insight ...idea查看源码 ...idea的debug模式查看调用栈, 可以追溯方法的调用连, 查看源码无障碍.  
  • 源码阅读步骤

    千次阅读 2017-02-22 11:40:22
    源码阅读,我觉得最核心有三点:技术基础+强烈的求知欲+耐心。  说到技术基础,我打个比方吧,如果你从来没有学过Java,或是任何一门编程语言如C++,一开始去啃《Core Java》,你是很难从中吸收到营养的,特别是...
  • 源码阅读系列:源码阅读方法

    万次阅读 2016-07-21 17:54:16
    2.UML能力在软件工程中,UML在软件的不同生命周期阶段扮演着非常重要的角色,没有好的UML水平,面对大型的项目源码会束手无策。3.对业务的理解如果你要阅读的项目业务性比较强,事先对业务有一定的了解是必须的。4....
  • 学习任务近期想增加部分源码阅读经验,提高自己在造轮子方面的实力,增长些在设计模式应用方面的编码能力,以及怀着向大佬们膜拜的心情,开始有计划地阅读Spring源码前期准备 以下几项准备事项,算是基本的日常...
  • 源码阅读的方法、误区以及三种境界

    万次阅读 多人点赞 2020-12-23 22:15:57
    摘要:笔者出品12个免费的源码分析专栏,覆盖RocketMQ、Kafka、Dubbo、Sentinel 等JAVA主流中间件,并借此实现年薪翻倍等成绩,对源码阅读有着深刻的理解。
  • kubernetes源码阅读之kubelet启动

    万次阅读 2016-08-19 16:30:13
    kubernetes源码阅读之kubelet启动
  • 为了读datax源码,要在本地idea 上进行debug 一,环境搭建 本地已经按照上文,安装好datax idea导入datax源码 启动类Engine 上配置参数 二,参数配置 vm option 需要写上你用maven打包后生成的target目录 -...
  • Go 源码阅读之 flag 包

    千次阅读 2021-01-20 22:56:09
    Go 源码阅读系列是我的源码阅读笔记。因为本人的电脑上 Go 的版本是1.13.4,所以就选择了该版本作为学习的版本。为此我在 Github 上 Fork 了 Go 的源码,并创建了 study1.13.4 分支,来记录对于源码的个人理解或者说...
  • 源码阅读工具UnderStand完整破解版

    热门讨论 2013-06-14 14:34:17
    一款很有用但却不出名的源码阅读工具,它的图表分析功能可以秒杀source insight,里面附带破解工具
  • Spring源码阅读(一):获取源码

    千次阅读 2018-05-07 17:31:35
    Spring源码阅读(一):获取源码Spring是2003年开始兴起的一个框架,发展到今天已是非常的强大。对于工作2、3年的人在面试的时候,可能都会被问到是否看过开源框架的源码,以及谈谈你看过源码之后的心得或者见解之类...
  • 刚才在论坛不经意间,看到有关源码阅读的帖子。回想自己前几年,阅读源码那种兴奋和成就感(1),不禁又有一种激动。源码阅读,我觉得最核心有三点:技术基础+强烈的求知欲+耐心。说到技术基础,我打个比方吧,如果你...
  • spring5.3.x源码阅读环境搭建

    千次阅读 2020-07-08 10:31:30
    spring5.3.x源码阅读环境搭建-gradle构建编译 文章目录spring5.3.x源码阅读环境搭建-gradle构建编译一、依赖工具二、下载源码三、开始构建四、编译源码五、源码测试六、问题及解决方案附:spring源代码各个模块作用...
  • ReentrantLock源码阅读

    万次阅读 2020-06-01 01:22:20
    ReentrantLock 是jdk提供的一种锁机制,该锁支持公平锁和非公平锁这两种。这两种锁的实现原理是 通过一 锁队列,公平锁是按照锁队列顺序执行,而非公平锁...接下来从源码入手,先查看NofairSync: new ReentrantLock()
  • Kafka2.0 源码阅读环境搭建 工欲善其事必先利其器,在阅读Kafka源码之前,首先第一步得搭建源码阅读环境,选择合适的工具可以使我们的学习效率更好,在这里我选用Intellij Idea 作为源码阅读环境的IDEA环境。其次...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 98,351
精华内容 39,340
关键字:

源码阅读