<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.9.0">Jekyll</generator><link href="https://side-wipe.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://side-wipe.github.io/" rel="alternate" type="text/html" /><updated>2021-02-02T14:10:37+08:00</updated><id>https://side-wipe.github.io/feed.xml</id><title type="html">SideWipe’s Blog</title><subtitle>闲庭信步笑看花开花落 宠辱不惊冷观云卷云舒</subtitle><author><name>SideWipe</name></author><entry><title type="html">详解springboot自动装配原理</title><link href="https://side-wipe.github.io/2020/07/04/springboot/" rel="alternate" type="text/html" title="详解springboot自动装配原理" /><published>2020-07-04T00:00:00+08:00</published><updated>2020-07-04T00:00:00+08:00</updated><id>https://side-wipe.github.io/2020/07/04/springboot</id><content type="html" xml:base="https://side-wipe.github.io/2020/07/04/springboot/">&lt;h1 id=&quot;springboot自动装配原理&quot;&gt;springboot自动装配原理&lt;/h1&gt;

&lt;h2 id=&quot;引述&quot;&gt;引述&lt;/h2&gt;

&lt;p&gt;    在SpringBoot项目中，无需各种配置文件，一个main方法，就能把项目启动起来，离不开一个重要的功能–自动装配，这也是springboot和springmvc最大的不同之处。&lt;/p&gt;

&lt;p&gt;    说到自动装配的原理，springboot启动类的注解@SpringBootApplication是一个组合注解，其中包含了三个重要注解：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;@SpringBootConfiguration：这个注解的底层是一个@Configuration注解，被@Configuration注解修饰的类是一个IOC容器，支持JavaConfig的方式来进行配置；&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;@ComponentScan：这个就是扫描注解的意思，默认扫描当前类所在的包及其子包下包含的注解，将@Controller/@Service/@Component/@Repository等注解加载到IOC容器中；&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;@EnableAutoConfiguration：这个注解表明启动自动装配，里面包含连个比较重要的注解@AutoConfigurationPackage和@Import。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;  &lt;img src=&quot;/images/posts/springboot/autoconfig1.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;这里重点分析@EnableAutoConfiguration，这也是自动装配的核心注解。&lt;/p&gt;

&lt;h2 id=&quot;enableautoconfiguration注解原理&quot;&gt;@EnableAutoConfiguration注解原理&lt;/h2&gt;
&lt;p&gt;    点击进入 @EnableAutoConfiguration注解，可以看到如下图所示，重点是AutoConfigurationImportSelector这个类，@import注解会在刷新容器时解析，并将import的类实例化注册到spring容器。&lt;/p&gt;

&lt;p&gt;  &lt;img src=&quot;/images/posts/springboot/autoconfig2.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;    AutoConfigurationImportSelecto实现了ImportSelector接口的selectImports方法，重点逻辑也在此方法中（如果要知道具体怎么实现的，需要看spring源码细节，在此不详细讨论），依次按照下图顺序点击进入核心方法：&lt;/p&gt;

&lt;p&gt;  &lt;img src=&quot;/images/posts/springboot/autoconfig3.png&quot; alt=&quot;&quot; /&gt;&lt;br /&gt;
  &lt;img src=&quot;/images/posts/springboot/autoconfig4.png&quot; alt=&quot;&quot; /&gt;&lt;br /&gt;
  &lt;img src=&quot;/images/posts/springboot/autoconfig5.png&quot; alt=&quot;&quot; /&gt;&lt;br /&gt;
  &lt;img src=&quot;/images/posts/springboot/autoconfig6.png&quot; alt=&quot;&quot; /&gt;&lt;br /&gt;
  &lt;img src=&quot;/images/posts/springboot/autoconfig7.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;   &lt;strong&gt;重点来了&lt;/strong&gt;，classLoader.getResources(FACTORIES_RESOURCE_LOCATION)，这句代码很显然是加载某个资源文件夹下面的数据，这个路径是：&lt;/p&gt;

&lt;p&gt;  &lt;img src=&quot;/images/posts/springboot/autoconfig8.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;    &lt;strong&gt;@EnableAutoConfiguration就是利用SpringFactoriesLoader机制加载自动装配配置的，它的配置数据在META-INF/spring.factories中，我们打开spring-boot-autoconfigure jar中的该文件，发现EnableAutoConfiguration对应着N多XXXAutoConfiguration配置类，我们截取几个重要的配置类如下(已经删除了很多)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;  &lt;img src=&quot;/images/posts/springboot/autoconfig9.png&quot; alt=&quot;&quot; /&gt;  &lt;br /&gt;
    可以看到Spring Boot提供了N多XXXAutoConfiguration类，有Spring Framework的、Web的、redis的、JDBC的等等。这些类都会在容器启动时，通过自动装配注解，解析并注册到spring容器，这样我们只需要简单的在yml中配置一下就可以直接使用了。&lt;/p&gt;

&lt;p&gt;     此外，@EnableAutoConfiguration注解会在容器启动时，扫描所有jar包下的META-INF/spring.factories，具体有什么用，接着往下看。&lt;/p&gt;

&lt;h2 id=&quot;基于springfactories的扩展机制&quot;&gt;基于spring.factories的扩展机制&lt;/h2&gt;
&lt;p&gt;     SpringFactoriesLoader机制加载自动装配也是spring集成第三方框架的一种方式，拿spring集成dubbo这个框架举例，我们在导入jar包后，只需要在yml文件中做如下配置即可&lt;/p&gt;

&lt;p&gt;  &lt;img src=&quot;/images/posts/springboot/autoconfig10.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;原理是什么呢？&lt;/p&gt;

&lt;p&gt;来到dubbo的源码包下，会发现在META-INF目录下也有一个spring.factories文件，截图如下：&lt;/p&gt;

&lt;p&gt;  &lt;img src=&quot;/images/posts/springboot/autoconfig11.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;     这里也有一个对EnableAutoConfiguration的配置，这是spring自定义的SPI服务。通过实现自动装配注解，在容器启动，解析启动类@EnableAutoConfiguration注解时，所有jar包下META-INF/spring.factories目录配置为EnableAutoConfiguration的类，都会被解析并注册到spring容器。&lt;/p&gt;

&lt;p&gt;    &lt;strong&gt;注册到spring容器后，下一个问题就是怎么自动读取我们yml文件的配置？&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;    还是以dubbo为例，我们点击进入spring.factories配置为EnableAutoConfiguration的类–DubboAutoConfiguration，如图：&lt;/p&gt;

&lt;p&gt;  &lt;img src=&quot;/images/posts/springboot/autoconfig12.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;上图标识的就是开启获取文件属性注解，点击进入&lt;/p&gt;

&lt;p&gt;  &lt;img src=&quot;/images/posts/springboot/autoconfig13.png&quot; alt=&quot;&quot; /&gt;    &lt;br /&gt;
     @ConfigurationProperties(DUBBO_PREFIX)这句注解就是把yml文件中，以dubbo为前缀的配置项，都映射到本类的实例化对象对应的属性中，同时在这里可以看到所有的可配置项。&lt;br /&gt;
     也就是说，在自动装配阶段，通过spring.factories配置，会随着容器启动解析dubbo框架中的自动装配类；这个类中会包含读取yml的逻辑。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;总结&lt;/em&gt;&lt;/strong&gt;&lt;br /&gt;
    Spring Boot自动装配的原理并不是非常复杂，其实背后的主要原理就是条件注解。&lt;br /&gt;
    当我们使用@EnableAutoConfiguration注解激活自动装配时，实质对应着很多XXXAutoConfiguration类在执行装配工作，这些XXXAutoConfiguration类是在spring-boot-autoconfigure jar中的META-INF/spring.factories文件中配置好的，@EnableAutoConfiguration通过SpringFactoriesLoader机制创建XXXAutoConfiguration这些bean。XXXAutoConfiguration的bean会依次执行并判断是否需要创建对应的bean注入到Spring容器中，在注册过程中会通过ConfigurationProperties注解，去读取yml文件中所需要的固定前缀的配置。&lt;br /&gt;
    在每个XXXAutoConfiguration类中，都会利用多种类型的条件注解@ConditionOnXXX对当前的应用环境做判断，如应用程序是否为Web应用、classpath路径上是否包含对应的类、Spring容器中是否已经包含了对应类型的bean。如果判断条件都成立，XXXAutoConfiguration就会认为需要向Spring容器中注入这个bean，否则就忽略。&lt;/p&gt;

&lt;h2 id=&quot;补充基于aop的扩展机制&quot;&gt;补充：基于aop的扩展机制&lt;/h2&gt;

&lt;p&gt;    springboot除了使用上述的这种扩展方式来集成很多第三方框架以外，还可以利用aop的方式来集成框架，比如常见的事务框架，线程池，Eureka等，只需要在配置类上面加@EnableTransactionManagement，@EnableAsync，@EnableEurekaServer启动注解，然后在对应方法上加上@Transactional，@Async注解即可使用（注意，事务框架比较特殊，它依赖于Aspectj，使用@EnableTransactionManagement注一定要加上@EnableAspectJAutoProxy）。&lt;/p&gt;

&lt;p&gt;    这种集成方式基于aop的方式扩展，在容器启动时，对切入点创建动态代理。拿@EnableAsync注解举例；@EnableAsync使用了这种方式，注解源码如下：&lt;br /&gt;
  &lt;img src=&quot;/images/posts/springboot/autoconfig14.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;    EnableAsync注解中导入了AsyncConfigurationSelector，AsyncConfigurationSelector通过条件来选择需要导入的配置类，继承AdviceModeImportSelector又实现了ImportSelector接口，接口重写selectImports方法进行事先条件判断PROXY或者ASPECTJ选择不同的配置类。 &lt;br /&gt;
  &lt;img src=&quot;/images/posts/springboot/autoconfig15.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;参考链接：&lt;a href=&quot;https://blog.csdn.net/m0_37595954/article/details/102720626&quot; title=&quot;神奇的@Enable*注解&quot;&gt;https://blog.csdn.net/m0_37595954/article/details/102720626&lt;/a&gt;&lt;/p&gt;</content><author><name>SideWipe</name></author><category term="Springboot" /><summary type="html">springboot自动装配原理</summary></entry><entry><title type="html">SpringCloud之sentinel(二)</title><link href="https://side-wipe.github.io/2020/06/15/sentinel/" rel="alternate" type="text/html" title="SpringCloud之sentinel(二)" /><published>2020-06-15T00:00:00+08:00</published><updated>2020-06-15T00:00:00+08:00</updated><id>https://side-wipe.github.io/2020/06/15/sentinel</id><content type="html" xml:base="https://side-wipe.github.io/2020/06/15/sentinel/">&lt;h1 id=&quot;sentinel规则持久化&quot;&gt;sentinel规则持久化&lt;/h1&gt;

&lt;h2 id=&quot;引述&quot;&gt;引述&lt;/h2&gt;

&lt;p&gt;    在springcloud之sentinel(一)中，说明了sentinel的用法。文章链接：&lt;a href=&quot;https://side-wipe.github.io/2020/06/13/sentinel/&quot;&gt;https://side-wipe.github.io/2020/06/13/sentinel/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;同时，也留下了一个问题，就是在服务重启或者sentinel-dashboard重启是，规则会丢失，那么sentinel规则如何持久化？本篇博客也是建立在上一篇博客的基础之上。&lt;/p&gt;

&lt;p&gt;本篇就将介绍：通过修改sentinel-dashboard源码，结合nacos注册中心对sentinel规则进行持久化。&lt;/p&gt;

&lt;h2 id=&quot;sentinel持久化&quot;&gt;sentinel持久化&lt;/h2&gt;

&lt;h3 id=&quot;1修改sentinel-dashboard源码&quot;&gt;1.修改sentinel-dashboard源码&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;修改后的源码已上传github，文章只能截图部分代码，请务必参照github上面的源码，地址链接：&lt;/strong&gt;  &lt;a href=&quot;https://github.com/side-wipe/sentinelSourceCode&quot;&gt;https://github.com/side-wipe/sentinelSourceCode&lt;/a&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;ul&gt;
  &lt;li&gt;首先下载源码&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;    我下载的是1.6.3版本，地址链接：&lt;a href=&quot;https://github.com/alibaba/Sentinel/releases/tag/1.6.3&quot; title=&quot;sentine-1.6.3&quot;&gt;https://github.com/alibaba/Sentinel/releases/tag/1.6.3&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;  &lt;img src=&quot;/images/posts/springCloud/ssc1.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;然后使用idea导入工作空间。&lt;/p&gt;

&lt;p&gt;  &lt;img src=&quot;/images/posts/springCloud/ssc2.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;主要修改的代码在sentinel-dashboard子项目中。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;修改推送/拉取源代码&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;    首先确定修改的位置在哪里。其实Alibaba在源码中已经留了缺口供持久化使用，也写了相应的例子，只不过都放在springboot项目的test文件夹下，如图：&lt;/p&gt;

&lt;p&gt;  &lt;img src=&quot;/images/posts/springCloud/ssc3.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;图中分别对应sentinel利用apollo，nacos，zookeeper三种注册中心进行持久化的代码，因此只需要依葫芦画瓢即可；先将nacos文件夹复制到发布目录的rule文件夹下。&lt;/p&gt;

&lt;p&gt;  &lt;img src=&quot;/images/posts/springCloud/ssc4.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;修改pom文件中的依赖权限，默认的是只在test时生效&lt;/p&gt;

&lt;p&gt;  &lt;img src=&quot;/images/posts/springCloud/ssc6.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;修改NacosConfig文件中的nacosConfigService方法，这个方法定义了sentinel将规则发送到注册中心的地址，因为我将注册中心部署到linux环境。&lt;/p&gt;

&lt;p&gt;  &lt;img src=&quot;/images/posts/springCloud/ssc5.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;关键代码如下 ：&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;拉取配置&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; /**
  * @author sidewipe
  * @desc 向nacos注册中心拉取配置规则
  */
 @Component(&quot;flowRuleNacosProvider&quot;)
 public class FlowRuleNacosProvider implements DynamicRuleProvider&amp;lt;List&amp;lt;FlowRuleEntity&amp;gt;&amp;gt; {
	
     @Autowired
     private ConfigService configService;    
	
     @Autowired
     private Converter&amp;lt;String, List&amp;lt;FlowRuleEntity&amp;gt;&amp;gt; converter;      //格式转换
	
     @Override
     public List&amp;lt;FlowRuleEntity&amp;gt; getRules(String appName) throws Exception {
         String rules = configService.getConfig(appName + NacosConfigUtil.FLOW_DATA_ID_POSTFIX,
             NacosConfigUtil.GROUP_ID, 3000);      //获取配置
         if (StringUtil.isEmpty(rules)) {
             return new ArrayList&amp;lt;&amp;gt;();
         }
         return converter.convert(rules);  //拉取的规则是一个json String，用转换器转化为List&amp;lt;FlowRuleEntity&amp;gt;
	

     }
 }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;2.推送配置&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;	/**
	 * @author sidewipe
	 * @desc 推送配置规则到nacos配置中心
	 */
	@Component(&quot;flowRuleNacosPublisher&quot;)
	public class FlowRuleNacosPublisher implements DynamicRulePublisher&amp;lt;List&amp;lt;FlowRuleEntity&amp;gt;&amp;gt; {
	
	    @Autowired
	    private ConfigService configService;
	    @Autowired
	    private Converter&amp;lt;List&amp;lt;FlowRuleEntity&amp;gt;, String&amp;gt; converter;  //转换器，将规则转换为json String类型方便推送到nacos配置中心
	
	    @Override
	    public void publish(String app, List&amp;lt;FlowRuleEntity&amp;gt; rules) throws Exception {
	        AssertUtil.notEmpty(app, &quot;app name cannot be empty&quot;);
	        if (rules == null) {
	            return;
	        }
	        configService.publishConfig(app + NacosConfigUtil.FLOW_DATA_ID_POSTFIX,
	            NacosConfigUtil.GROUP_ID, converter.convert(rules));  推送配置
	    }
	}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;
&lt;p&gt;至此，推送和拉取部分的代码就改的差不多了，其实就是把本是放在test文件夹中的几个文件复制出来，然后改一下注册中心地址就可以了，没有什么需要改的东西。但是这些代码需要在controller层调用，这样才能将sentinel的规则推送到配置中心，接下来就要去controller包中改代码。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;修改controller&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;首先看一张图：&lt;/p&gt;

&lt;p&gt;  &lt;img src=&quot;/images/posts/springCloud/ssc7.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;     图中标识的各种限流的controller，需要使用上面我们写的推送/拉取方式，因此都需要修改，这里以修改FlowControllerV1.class为例。&lt;/p&gt;

&lt;p&gt;1.注入上一步新建的两个工具类，取代sentinelApiClient：&lt;/p&gt;

&lt;p&gt;  &lt;img src=&quot;/images/posts/springCloud/ssc8.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;2.重新写一个publishRules()推送规则方法&lt;/p&gt;

&lt;p&gt;  &lt;img src=&quot;/images/posts/springCloud/ssc9.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;3.然后将所有调用原本publishRules()的地方改为调用新的方法，举例如下：&lt;/p&gt;

&lt;p&gt;  &lt;img src=&quot;/images/posts/springCloud/ssc10.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;4.修改拉取规则的方法，改为从nacos配置中心拉取&lt;/p&gt;

&lt;p&gt;  &lt;img src=&quot;/images/posts/springCloud/ssc11.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;修改完成！&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2sentinel-pro工程修改&quot;&gt;2.sentinel-pro工程修改&lt;/h3&gt;

&lt;hr /&gt;

&lt;p&gt;   &lt;strong&gt;修改自己的springcloud工程，工程链接：&lt;/strong&gt;&lt;a href=&quot;https://github.com/side-wipe/sentinel-pro&quot;&gt;https://github.com/side-wipe/sentinel-pro&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;只需要修改yml配置文件，为sentinel添加数据源，数据源就是nacos注册中心的地址，这样就使得项目会去nacos注册中心拉取sentinel配置规则，修改如图：&lt;/p&gt;

&lt;p&gt;  &lt;img src=&quot;/images/posts/springCloud/ssc12.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;3测试&quot;&gt;3.测试&lt;/h3&gt;

&lt;hr /&gt;

&lt;p&gt;修改完成后，可以在本地启动springboot项目进行测试；我在测试完之后，将sentinel-dashboard源码打成jar包（sentinel-dashboard.jar），重新部署在linux中，上面图示的配置地址都是我的linux服务器地址。&lt;/p&gt;

&lt;p&gt;上传到linux服务器，输入&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;nohup java -Dserver.port=8888 -Dcsp.sentinel.dashboard.server=localhost:8888 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard.jar &amp;amp;   
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;启动后，启动sentinel-pro工程的订单中心（order-center）和产品中心（product-center），和上一篇博客步骤一致；接着，在sentinel-dashboard添加限流规则：&lt;/p&gt;

&lt;p&gt;  &lt;img src=&quot;/images/posts/springCloud/ssc13.png&quot; alt=&quot;&quot; /&gt;   &lt;br /&gt;
  &lt;img src=&quot;/images/posts/springCloud/ssc14.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;然后去nacos配置中心会发现一个新的配置文件，点击详情，可以看到我们配置的阈值参数：&lt;/p&gt;

&lt;p&gt;  &lt;img src=&quot;/images/posts/springCloud/ssc15.png&quot; alt=&quot;&quot; /&gt;     &lt;br /&gt;
  &lt;img src=&quot;/images/posts/springCloud/ssc16.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;此时，就表示，流控规则已经推送到配置中心了；连续访问页面http://localhost:8082/selectOrderInfoById/1发现被限流了；并且重启服务，限流规则也不会消失，完成任务。&lt;/p&gt;

&lt;h3 id=&quot;4补充说明&quot;&gt;4.补充说明&lt;/h3&gt;
&lt;hr /&gt;

&lt;p&gt;对限流，热点参数，降级等都是一样的修改方式，本文叙述只是拿限流举例；我在修改源码时，也同时修改了其他的几种限流降级等策略，并对拉取和推送方法进行了封装，具体完整版参考我的github：
&lt;a href=&quot;https://github.com/side-wipe/sentinelSourceCode&quot;&gt;https://github.com/side-wipe/sentinelSourceCode&lt;/a&gt;&lt;/p&gt;</content><author><name>SideWipe</name></author><category term="SpringCloud" /><summary type="html">sentinel规则持久化</summary></entry><entry><title type="html">SpringCloud之sentinel(一)</title><link href="https://side-wipe.github.io/2020/06/13/sentinel/" rel="alternate" type="text/html" title="SpringCloud之sentinel(一)" /><published>2020-06-13T00:00:00+08:00</published><updated>2020-06-13T00:00:00+08:00</updated><id>https://side-wipe.github.io/2020/06/13/sentinel</id><content type="html" xml:base="https://side-wipe.github.io/2020/06/13/sentinel/">&lt;h1 id=&quot;springcloud中的流量哨兵sentinel&quot;&gt;springcloud中的流量哨兵sentinel&lt;/h1&gt;

&lt;h2 id=&quot;sentinel简介&quot;&gt;sentinel简介&lt;/h2&gt;

&lt;p&gt;    在springcloud开发中，针对高并发系统经常会有限流降级的需求；提到限流降级，最常见的是netflix的hystrix和Alibaba的sentinel，sentinel相比hystrix功能更加强大，比如它可以将流控粒度细化控制到某一个具体接口甚至是接口传过来的具体数值（热点数据）；但是sentinel也有一定的缺点，就是不能持久化，关于如何解决持久化问题，参考我的另一篇博客，SpringCloud之sentinel(二)；链接：&lt;a href=&quot;https://side-wipe.github.io/2020/06/15/sentinel/&quot;&gt;https://side-wipe.github.io/2020/06/15/sentinel/&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;本篇就将介绍sentinel基本用法。&lt;/p&gt;

&lt;h2 id=&quot;sentinel基本用法&quot;&gt;sentinel基本用法&lt;/h2&gt;

&lt;h3 id=&quot;1环境准备&quot;&gt;1.环境准备&lt;/h3&gt;

&lt;hr /&gt;
&lt;p&gt;     需要准备三个东西，springcloud的注册中心nacos，数据库mysql，以及sentinel-dashboard。&lt;br /&gt;
     简单说一下sentinel-dashboard，这是一个Alibaba开源的项目，简单说就是一个springboot工程（jar包），安装启动后做为一个sentinel控制台使用，我们想进行流控的微服务工程也同样要注册到这个控制台，可以通过这个控制台来配置你想要的流控降级规则。&lt;br /&gt;
     这三个东西我都是配置在linux环境下，也可以配置在windows环境下，具体相关配置操作百度即可，都比较简单就不在这里详细说了。需要的工具包（nacos和sentinel-dashboard）以及sql文件在我的github项目目录下（&lt;a href=&quot;https://github.com/side-wipe/sentinel-pro/tree/master/tools&quot; title=&quot;工具包下载&quot;&gt;https://github.com/side-wipe/sentinel-pro/tree/master/tools&lt;/a&gt;），可自行下载。&lt;/p&gt;

&lt;h3 id=&quot;2使用示例&quot;&gt;2.使用示例&lt;/h3&gt;

&lt;hr /&gt;
&lt;h4 id=&quot;这里我们结合feign来进行使用完整代码已上传github地址httpsgithubcomside-wipesentinel-pro&quot;&gt;这里我们结合feign来进行使用，完整代码已上传github；地址：&lt;a href=&quot;https://github.com/side-wipe/sentinel-pro/&quot; title=&quot;sentinel-pro&quot;&gt;https://github.com/side-wipe/sentinel-pro/&lt;/a&gt;&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;项目结构简单说明：&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;  &lt;img src=&quot;/images/posts/springCloud/sentinel1.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;    如上所示：根据springcloud的架构习惯，common工程中都是一些项目通用的实体类；mapper-dao就是将调用mysql的dao层作为一个工程；feign-api工程就是所有feign调用的接口；这三个子工程都是给其他工程依赖的，无需启动。&lt;br /&gt;
    建立两个微服务，order-center(订单服务)，product-center(产品服务)；模拟订单服务通过feign调用产品服务，并对订单服务就行限流降级控制。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;核心代码 
    除了基础的配置外（数据源，注册中心等），还需要sentinel的配置；既然是对订单服务进行配置，就要在订单服务的yml文件中配置sentinel，如下图：&lt;br /&gt;
&lt;strong&gt;需要注意的是，sentinel要配置两个端口，一个端口是微服务用来注册到sentinel控制台，一个是用于微服务和控制台通信&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;  &lt;img src=&quot;/images/posts/springCloud/sentinel2.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;配置完成后，接下来只需要对feign-api的api接口进行改造就可以了；可以看到，这种方式对业务代码入侵非常低。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;@FeignClient(name = &quot;product-center&quot;,fallbackFactory = ProductCenterFeignApiWithSentielFallbackFactory.class)
public interface ProductCenterFeignApiWithSentinel {

    /**
     * 声明式接口,远程调用http://product-center/selectProductInfoById/{productNo}
     * @param productNo
     * @return
     */
    @RequestMapping(&quot;/selectProductInfoById/{productNo}&quot;)
    ProductInfo selectProductInfoById(@PathVariable(&quot;productNo&quot;) String productNo);

}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;ProductCenterFeignApiWithSentielFallbackFactory是sentinel包中提供给我们的一个回调接口。可通过实现此接口，当/selectProductInfoById/{productNo}路径被限流处理时，返回的异常可以在回调函数中处理。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;@Component
@Slf4j
public class ProductCenterFeignApiWithSentielFallbackFactory implements FallbackFactory&amp;lt;ProductCenterFeignApiWithSentinel&amp;gt; {
    @Override
	//可以在这里捕获返回的异常，限流和降级的异常是不一样的，可以在这里区分。
    public ProductCenterFeignApiWithSentinel create(Throwable throwable) {  
        return new ProductCenterFeignApiWithSentinel(){  


            @Override
            public ProductInfo selectProductInfoById(String productNo) {
                ProductInfo productInfo = new ProductInfo();
                if (throwable instanceof FlowException){
                    productInfo.setProductName(&quot;我是限流的默认商品&quot;);
                }
                else {
                    productInfo.setProductName(&quot;我是降级的默认商品&quot;);

                }
                return productInfo;
            }
        };
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;完整的代码请到github下载。&lt;/p&gt;

&lt;h3 id=&quot;3效果展示&quot;&gt;3.效果展示&lt;/h3&gt;

&lt;hr /&gt;

&lt;ul&gt;
  &lt;li&gt;启动工程&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;    首先启动nacos和sentinel-dashboard（这两个我已经在linux环境下配置好了），启动后截图如下：&lt;/p&gt;

&lt;p&gt;这里我用nginx和nacos在linux下配置了一个注册中心集群，一共三个节点（配置方式可参考我的另一篇博客），此时微服务工程未启动，可以看到服务列表为空。&lt;/p&gt;

&lt;p&gt;nacos控制台：&lt;/p&gt;

&lt;p&gt;  &lt;img src=&quot;/images/posts/springCloud/sentinel3.png&quot; alt=&quot;&quot; /&gt;  &lt;br /&gt;
  &lt;img src=&quot;/images/posts/springCloud/sentinel4.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;sentinel控制台：&lt;/p&gt;

&lt;p&gt;linux环境下，下载sentinel-dashboard-1.6.3.jar后，输入&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;nohup java -Dserver.port=8888 -Dcsp.sentinel.dashboard.server=localhost:8888 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.6.3.jar  
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;即可，注意替换成自己的机器ip和端口&lt;/p&gt;

&lt;p&gt;  &lt;img src=&quot;/images/posts/springCloud/sentinel5.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;启动订单微服务和产品微服务，此时的nacos注册中心：&lt;/p&gt;

&lt;p&gt;  &lt;img src=&quot;/images/posts/springCloud/sentinel6.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;此时的sentinel控制台：&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;（注意：可能启动后还是没有，用浏览器或者postman调用一次接口就有了）&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;  &lt;img src=&quot;/images/posts/springCloud/sentinel7.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;访问订单服务：&lt;/p&gt;

&lt;p&gt;  &lt;img src=&quot;/images/posts/springCloud/sentinel10.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;添加限流规则&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;针对order-center进行流控： 配置规则为，每秒只允许一个访问通过，其他的都会进行拦截。&lt;/p&gt;

&lt;p&gt;  &lt;img src=&quot;/images/posts/springCloud/sentinel8.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;  &lt;img src=&quot;/images/posts/springCloud/sentinel9.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;规则配置完成，再次访问，并不断刷新页面，会出现如图所示：&lt;/p&gt;

&lt;p&gt;  &lt;img src=&quot;/images/posts/springCloud/sentinel11.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;可以看到，发生了限流提示，而这个提示正是我们在feign-api工程中处理的，可以debug看一下：&lt;/p&gt;

&lt;p&gt;  &lt;img src=&quot;/images/posts/springCloud/sentinel12.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;同样的，如果在sentinel控制台设置降级规则，则会返回降级异常，根据我们的处理，返回的参数也有所不同。&lt;/p&gt;

&lt;p&gt;    当然，sentinel的可配置规则不仅限于此，还有很多强大的功能，这里就不一一展示；但是也有一个问题，前文也说到了，就是不能持久化，通俗的说，就是无论你的控制台重启还是项目重启，在控制台配置的规则都会全部丢失，这显然是一个很大的问题，下一篇文章将会简述如何持久化。&lt;/p&gt;</content><author><name>SideWipe</name></author><category term="SpringCloud" /><summary type="html">springcloud中的流量哨兵sentinel</summary></entry><entry><title type="html">SpringCloud之ribbon</title><link href="https://side-wipe.github.io/2020/06/11/ribbon/" rel="alternate" type="text/html" title="SpringCloud之ribbon" /><published>2020-06-11T00:00:00+08:00</published><updated>2020-06-11T00:00:00+08:00</updated><id>https://side-wipe.github.io/2020/06/11/ribbon</id><content type="html" xml:base="https://side-wipe.github.io/2020/06/11/ribbon/">&lt;h1 id=&quot;springcloud中负载均衡工具ribbon用法及原理&quot;&gt;springcloud中负载均衡工具ribbon用法及原理&lt;/h1&gt;

&lt;h2 id=&quot;1ribbon用法&quot;&gt;1.ribbon用法&lt;/h2&gt;
&lt;p&gt;在这里我使用restTemplate和ribbon结合使用为例，前提是服务到注册到同一注册中心，本例使用的是nacos。&lt;/p&gt;

&lt;h3 id=&quot;基本用法&quot;&gt;&lt;strong&gt;基本用法&lt;/strong&gt;&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;一般情况下，我们使用restTemplate做服务调用，代码如下：&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  ResponseEntity&amp;lt;ProductInfo&amp;gt; responseEntity= restTemplate.getForEntity(uri+orderInfo.getProductNo(), ProductInfo.class);

  ProductInfo productInfo = responseEntity.getBody();

  if(productInfo == null) {
 	   return &quot;无数据&quot;;
  } 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;当调用的服务有多个实例，需要做负载均衡是，则引入了ribbon，使用如下：&lt;/p&gt;

&lt;p&gt;  &lt;img src=&quot;/images/posts/springCloud/ribbon2.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;     使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@LoadBalnaced&lt;/code&gt;注解，那么返回的restTemplate注解就是一个具有负载算法的对象；在调用服务时，会从注册中心获取被调用方的所有可调用实例列表，并依据负载均衡算法进行服务调用。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;如果需要指定负载均衡算法，则在配置类中返回一个实现IRule接口的负载算法对象即可：&lt;br /&gt;
  &lt;img src=&quot;/images/posts/springCloud/ribbon5.png&quot; alt=&quot;&quot; /&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;问题：那么ribbon是怎么让restTemplate具有负载均衡功能的呢？后面再分析原理&lt;/strong&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;进阶用法&quot;&gt;&lt;strong&gt;进阶用法&lt;/strong&gt;&lt;/h3&gt;

&lt;p&gt;    在一些业务场景中，我们希望对不同的微服务集群使用不同的负载均衡算法，甚至某些服务集群使用自定义的负载均衡算法。比如：我对订单服务集群使用轮询负载算法，对付费服务集群使用随机负载算法，对积分服务集群使用自定义的权重负载算法，这时我们有两种方式可以实现：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;基于yml文件配置的方法（推荐的方式）： 通过yml配置的方式，对不同的微服务集群采用不同的负载算法，ribbon中自带的负载算法均是实现IRule接口。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;    IRule接口是一个ribbon-balancer包下的一个上层接口，大致结构如下，使用的是模板方法模式，其实现类实现了不同的负载均衡算法：&lt;/p&gt;

&lt;p&gt;类图如下：&lt;/p&gt;

&lt;p&gt;  &lt;img src=&quot;/images/posts/springCloud/ribbon6.png&quot; alt=&quot;&quot; /&gt;&lt;br /&gt;
    我们可以基于此接口实现自己的负载均衡算法： 如下：&lt;/p&gt;

&lt;p&gt;自定义负载算法类：TulingWeightedRule.class （自定义实现权重算法：nacos注册中心可以给集群中的实例设置权重）&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;package com.tuling.myrule;

import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.cloud.nacos.ribbon.NacosServer;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.naming.NamingService;
import com.alibaba.nacos.api.naming.pojo.Instance;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.BaseLoadBalancer;
import com.netflix.loadbalancer.Server;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;

/**
 */
@Slf4j
public class TulingWeightedRule extends AbstractLoadBalancerRule {

    @Autowired
    private NacosDiscoveryProperties discoveryProperties;

    @Override
    public void initWithNiwsConfig(IClientConfig clientConfig) {
        //读取配置文件并且初始化,ribbon内部的 几乎用不上
    }


    @Override
    public Server choose(Object key) {
        try {
            log.info(&quot;key:{}&quot;,key);
            BaseLoadBalancer baseLoadBalancer = (BaseLoadBalancer) this.getLoadBalancer();
            log.info(&quot;baseLoadBalancer---&amp;gt;:{}&quot;,baseLoadBalancer);

            //获取微服务的名称
            String serviceName = baseLoadBalancer.getName();

            //获取Nocas服务发现的相关组件API
            NamingService namingService =  discoveryProperties.namingServiceInstance();

            //获取 一个基于nacos client 实现权重的负载均衡算法
            Instance instance = namingService.selectOneHealthyInstance(serviceName);

            //返回一个server
            return new NacosServer(instance);
        } catch (NacosException e) {
            log.error(&quot;自定义负载均衡算法错误&quot;);
        }
        return null;
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;在yml配置中只需要指定对应的配置算法包位置即可，如下图所示：&lt;/p&gt;

&lt;p&gt;  &lt;img src=&quot;/images/posts/springCloud/ribbon3.png&quot; alt=&quot;&quot; /&gt; &lt;br /&gt;
这种配置方式不需要再添加其他的java代码，是最常用也是推荐的方式。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;    在真实开发中一般我们会使用feign代替restTemplate+ribbon，feign是对ribbon的封装，针对feign调用配置负载均衡算法，其实也是对ribbon的配置，配置方法和上图一致&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;通过javaConfig的方式配置
    定义负载算法的配置类：跟基础用法的原理一样，通过配置类向容器中加入实现IRule接口的负载算法即可。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这种方式需要增加一个配置类。并使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@RibbonClients&lt;/code&gt;注解，代码如下：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;	package com.tuling.config; 
	import org.springframework.cloud.client.loadbalancer.LoadBalanced;
	import org.springframework.context.annotation.Bean;`
	import org.springframework.context.annotation.Configuration;
	import org.springframework.web.client.RestTemplate;


	@Configuration
	public class WebConfig {
	
	@LoadBalanced
	@Bean
	public RestTemplate restTemplate( ) {
		return new RestTemplate();
		}
	} 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;负载算法配置总类：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;package com.tuling.config;

import com.ribbonconfig.GlobalRibbonConfig;
import com.ribbonconfig.PayCenterRibbonConfig;
import com.ribbonconfig.ProductCenterRibbonConfig;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.cloud.netflix.ribbon.RibbonClients;
import org.springframework.context.annotation.Configuration;


@Configuration
@RibbonClients(
   		defaultConfiguration = GlobalRibbonConfig.class, 	//全局默认算法
value = {
    @RibbonClient(name = &quot;product-center&quot;,configuration ProductCenterRibbonConfig.class),    //服务集群product-center使用的负载算法
    @RibbonClient(name = &quot;pay-center&quot;,configuration = PayCenterRibbonConfig.class) //服务集群pay-center使用的负载算法
})

public class CustomRibbonConfig {

}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;服务集群product-center对应的算法配置：ProductCenterRibbonConfig.class:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;package com.ribbonconfig;

import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ProductCenterRibbonConfig {

	@Bean
	public IRule randomRule() {
		return new RandomRule();
	}
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;服务集群pay-center对应的算法配置：PayCenterRibbonConfig.class:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;package com.ribbonconfig;

import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RoundRobinRule;
import org.springframework.context.annotation.Configuration;


@Configuration
public class PayCenterRibbonConfig {

    public IRule roundRobinRule() {
        return new RoundRobinRule();
    }
} 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;全局默认使用自定义的负载算法（比如需要实现权重算法：nacos注册中心可以给集群中的实例设置权重）：&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;全局默认算法对应配置： GlobalRibbonConfig.class&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;package com.ribbonconfig;

import com.netflix.loadbalancer.IRule;
import com.tuling.myrule.TheSameClusterPriorityRule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


@Configuration
public class GlobalRibbonConfig {

    @Bean
    public IRule theSameClusterPriorityRule() {
        return new TulingWeightedRule();  //上文中自定义的负载算法	     
	}
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;综上，就通过javaConfig的方式，实现了指定负载均衡算法，并且可以指定使用自定义的负载算法。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;2ribbon的原理&quot;&gt;2.ribbon的原理&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;先通过一个例子来说明，也可以理解为手写实现一个简单的ribbon嵌入到restTemplate中：&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;首先还是基础的restTemplate配置类：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;package com.tuling.config;

import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;


@Configuration
public class WebConfig {

    @Bean
    public RestTemplate restTemplate(DiscoveryClient discoveryClient) {
        return new TulingRestTemplate(discoveryClient);
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;    可以看到这次我不是直接返回一个springcloud包中的RestTemplate的实例，而是一个自定义的TulingRestTemplate，该类代码如下：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;package com.tuling.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.http.HttpMethod;
import org.springframework.http.client.ClientHttpRequest;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.web.client.*;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;
import java.util.Random;

/**
 * 根据RestTemplate特性自己改造
 */
@Slf4j
public class TulingRestTemplate extends RestTemplate {

    private DiscoveryClient discoveryClient;

    public TulingRestTemplate (DiscoveryClient discoveryClient) {
        this.discoveryClient = discoveryClient;
    }

    protected &amp;lt;T&amp;gt; T doExecute(URI url, @Nullable HttpMethod method, @Nullable RequestCallback requestCallback,
                              @Nullable ResponseExtractor&amp;lt;T&amp;gt; responseExtractor) throws RestClientException {

        Assert.notNull(url, &quot;URI is required&quot;);
        Assert.notNull(method, &quot;HttpMethod is required&quot;);
        ClientHttpResponse response = null;
        try {

            log.info(&quot;请求的url路径为:{}&quot;,url);
            //把服务名 替换成我们的IP
            url = replaceUrl(url);

            log.info(&quot;替换后的路径:{}&quot;,url);

            ClientHttpRequest request = createRequest(url, method);
            if (requestCallback != null) {
                requestCallback.doWithRequest(request);
            }
            response = request.execute();
            handleResponse(url, method, response);
            return (responseExtractor != null ? responseExtractor.extractData(response) : null);
        }
        catch (IOException ex) {
            String resource = url.toString();
            String query = url.getRawQuery();
            resource = (query != null ? resource.substring(0, resource.indexOf('?')) : resource);
            throw new ResourceAccessException(&quot;I/O error on &quot; + method.name() +
                    &quot; request for \&quot;&quot; + resource + &quot;\&quot;: &quot; + ex.getMessage(), ex);
        } finally {
            if (response != null) {
                response.close();
            }
        }
    }


    /**
     * 方法实现说明:把微服务名称  去注册中心拉取对应IP进行调用
     * http://product-center/selectProductInfoById/1
     * @param url:请求的url
     * @return:
     * @exception:
     */
    private URI replaceUrl(URI url){

        //1:从URI中解析调用的调用的serviceName=product-center
        String serviceName = url.getHost();
        log.info(&quot;调用微服务的名称:{}&quot;,serviceName);

        //2:解析我们的请求路径 reqPath= /selectProductInfoById/1
        String reqPath = url.getPath();
        log.info(&quot;请求path:{}&quot;,reqPath);


        //通过微服务的名称去nacos服务端获取 对应的实例列表
        List&amp;lt;ServiceInstance&amp;gt; serviceInstanceList = discoveryClient.getInstances(serviceName);
        if(serviceInstanceList.isEmpty()) {
            throw new RuntimeException(&quot;没有可用的微服务实例列表:&quot;+serviceName);
        }

        String serviceIp = chooseTargetIp(serviceInstanceList);

        String source = serviceIp+reqPath;
        try {
            return new URI(source);
        } catch (URISyntaxException e) {
            log.error(&quot;根据source:{}构建URI异常&quot;,source);
        }
        return url;
    }

    /**
     * 方法实现说明:从服务列表中 随机选举一个ip
     * @param serviceInstanceList 服务列表
     * @return: 调用的ip
     * @exception:
     */
    private String chooseTargetIp(List&amp;lt;ServiceInstance&amp;gt; serviceInstanceList) {
        //采取随机的获取一个
        Random random = new Random();
        Integer randomIndex = random.nextInt(serviceInstanceList.size());
        String serviceIp = serviceInstanceList.get(randomIndex).getUri().toString();
        log.info(&quot;随机选举的服务IP:{}&quot;,serviceIp);
        return serviceIp;
    }
    
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;    代码很多，思路就是通过继承RestTemplate类，并复写doExecute方法（在RestTemplate源码中可以看到，所有的调用方法最后都会走doExecute方法）。&lt;/p&gt;

&lt;p&gt;    大体意思是在调用时，去拉取注册中心该服务的服务实例列表，然后通过random随机算法来选出一个服务实例来进行调用。这样就相当于对RestTemplate进行了一个扩展，让它在调用服务时，拥有了负载均衡的功能。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;那么ribbon也是通过扩展restTemplate来实现负载均衡的。&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;跟踪源码可以看到，ribbon是通过在spring容器启动时，向restTemplate中加入interceptor拦截器的方式来为restTemplate添加负载均衡功能，截图如下： 
  &lt;img src=&quot;/images/posts/springCloud/ribbon1.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;大体的思想就是这样！&lt;/p&gt;</content><author><name>SideWipe</name></author><category term="SpringCloud" /><summary type="html">springcloud中负载均衡工具ribbon用法及原理</summary></entry><entry><title type="html">nginx安装配置</title><link href="https://side-wipe.github.io/2020/03/06/nginx/" rel="alternate" type="text/html" title="nginx安装配置" /><published>2020-03-06T00:00:00+08:00</published><updated>2020-03-06T00:00:00+08:00</updated><id>https://side-wipe.github.io/2020/03/06/nginx</id><content type="html" xml:base="https://side-wipe.github.io/2020/03/06/nginx/">&lt;h1 id=&quot;linux下nginx安装并配置负载均衡&quot;&gt;linux下nginx安装并配置负载均衡&lt;/h1&gt;

&lt;p&gt;记一次linux下配置负载均衡踩坑，先把安装过程大致梳理一遍。&lt;/p&gt;

&lt;h2 id=&quot;1安装nginx&quot;&gt;1.安装nginx&lt;/h2&gt;
&lt;h3 id=&quot;下载nginx&quot;&gt;下载nginx&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wget -c https://nginx.org/download/nginx-1.4.6.tar.gz &lt;/code&gt; 
wget命令下载到当前文件夹，如果没有wget命令，则使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;yum install wget&lt;/code&gt;安装。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tar -zxvf nginx-1.4.6.tar.gz &lt;/code&gt;
解压下载的nginx压缩文件
    &lt;h3 id=&quot;安装nginx环境&quot;&gt;安装nginx环境&lt;/h3&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt; ./configure --prefix=/usr/local/nginx&lt;/code&gt; （此为你的nginx安装目录）
使用默认configure文件配置nginx，如果没有部署好环境，此步骤一定会报错，（总之就是报错缺少什么就安装什么。）如下：&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;  &lt;img src=&quot;/images/posts/linux/nginx1.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;需要安装的依赖主要有：&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;yum install gcc-c++ &lt;/code&gt;&lt;/p&gt;

    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;yum install -y pcre pcre-devel &lt;/code&gt;&lt;/p&gt;

    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;yum install -y zlib zlib-devel &lt;/code&gt;&lt;/p&gt;

    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;yum install -y openssl openssl-devel&lt;/code&gt;&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;安装成功后，再次输入&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./configure --prefix=/usr/local/nginx&lt;/code&gt;即可配置成功&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;make &amp;amp;&amp;amp; make install&lt;/code&gt;完成编译安装&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;2配置nginx负载均衡&quot;&gt;2.配置nginx负载均衡&lt;/h2&gt;
&lt;p&gt;大概的负载均衡配置如下图：&lt;/p&gt;

&lt;p&gt;  &lt;img src=&quot;/images/posts/linux/nginx2.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;注意：一开始我这样配置，没有任何问题，但是我在浏览器访问http://192.168.137.21:8847/nacos_cluster/nacos一直访问不了，报400错误，也google了很多办法，都没有解决。最终我把nacos_cluster的下划线改掉就好了，为什么呢？我用的nginx版本是1.4，可能由于源码的原因，无法解析带有下划线的地址（很坑），最终配置如下：&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;  &lt;img src=&quot;/images/posts/linux/nginx3.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;访问地址为http://192.168.137.21:8847/cluster/nacos&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;注意：修改配置后需要重新启动或者动态加载，具体如下：&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;nginx启动: ./sbin/nginx&lt;/p&gt;

&lt;p&gt;nginx停止: ./sbin/nginx -s stop&lt;/p&gt;

&lt;p&gt;nginx配置动态加载: ./sbin/nginx -s reload（在nginx运行时才能使用此命令）&lt;/p&gt;</content><author><name>SideWipe</name></author><category term="Linux" /><summary type="html">linux下nginx安装并配置负载均衡</summary></entry><entry><title type="html">CentOS7网络配置</title><link href="https://side-wipe.github.io/2020/02/06/linux/" rel="alternate" type="text/html" title="CentOS7网络配置" /><published>2020-02-06T00:00:00+08:00</published><updated>2020-02-06T00:00:00+08:00</updated><id>https://side-wipe.github.io/2020/02/06/linux</id><content type="html" xml:base="https://side-wipe.github.io/2020/02/06/linux/">&lt;h1 id=&quot;vmware虚拟机中centos操作系统网络设置和问题解决&quot;&gt;VMware虚拟机中，CentOS操作系统网络设置和问题解决&lt;/h1&gt;

&lt;p&gt;在VMware中装好CentOS7后，我们一开始是上不了网的&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/linux/vmware1.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;需要进行一些网络设置，具体如下：&lt;/p&gt;

&lt;h2 id=&quot;1设置vmware虚拟网络&quot;&gt;1.设置VMware虚拟网络&lt;/h2&gt;
&lt;p&gt;    把系统的IP设置为静态的，设置步骤如下：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;点击VMware虚拟机左上角的“编辑”，选择“虚拟网络编译器”。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;选中VMnet8（NAT模式），再点击右侧的“NAT设置”此时会看到如下界面：按照图中所示进行设置，子网ip为你的linux虚拟ip的网段&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;需要注意的是，虚拟机linux的ip地址，子网ip，网关ip，起始ip地址均要在同一网段。如图中，这些ip均在192.168.137.0，后面我会将IP地址设置为192.168.137.21&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;  &lt;img src=&quot;/images/posts/linux/vmware2.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;点击NAT设置，按照图中所示设置&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;  &lt;img src=&quot;/images/posts/linux/vmware3.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;点击DHCP设置&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;  &lt;img src=&quot;/images/posts/linux/vmware4.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;    &lt;strong&gt;特别提醒：设置完之后要用命令service network restart重启网卡&lt;/strong&gt;&lt;/p&gt;

&lt;h2 id=&quot;2进入centos设置虚拟机的ip地址&quot;&gt;2.进入centOS，设置虚拟机的IP地址&lt;/h2&gt;
&lt;p&gt;    在操作系统中设置虚拟机的IP地址，网络配置&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;cd /etc/sysconfig/network-scripts/ 进入此目录下&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;  &lt;img src=&quot;/images/posts/linux/centOS1.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;    &lt;strong&gt;特别提醒：如上图所示，箭头所指就是需要修改的网络配置文件。由于linux操作系统的版本问题，网络配置文件名字可能有所不同，可能是名为ifcfg-ens33，ifcfg-enth0等；在使用vi编辑该文件时，最好进到该目录下看下默认的文件是哪一个；直接vi编辑，如果该文件不存在，会自动创建一个新的空文件，那么你的配置写在新的文件中，系统读取的还是默认的文件，这样你的配置将无法生效。总之，保证在此目录下只有一个系统默认的文件是最稳妥的&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;输入 vi ifcfg-ens33 编辑文件，设置如下：&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;  &lt;img src=&quot;/images/posts/linux/centOS2.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;  HDADDR为硬件地址，输入 ifconfig 指令可查询&lt;/p&gt;

&lt;p&gt;  &lt;img src=&quot;/images/posts/linux/centOS3.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;hr /&gt;
&lt;h2 id=&quot;附上常见网络设置问题解决方式基本涵盖所有情况httpsblogcsdnnetyyy987456123articledetails97142858&quot;&gt;附上常见网络设置问题解决方式（基本涵盖所有情况）：&lt;a href=&quot;https://blog.csdn.net/yyy987456123/article/details/97142858&quot;&gt;https://blog.csdn.net/yyy987456123/article/details/97142858&lt;/a&gt;&lt;/h2&gt;</content><author><name>SideWipe</name></author><category term="Linux" /><summary type="html">VMware虚拟机中，CentOS操作系统网络设置和问题解决</summary></entry><entry><title type="html">Java对象头</title><link href="https://side-wipe.github.io/2020/02/04/java/" rel="alternate" type="text/html" title="Java对象头" /><published>2020-02-04T00:00:00+08:00</published><updated>2020-02-04T00:00:00+08:00</updated><id>https://side-wipe.github.io/2020/02/04/java</id><content type="html" xml:base="https://side-wipe.github.io/2020/02/04/java/">&lt;h1 id=&quot;java对象由什么组成java对象的内存大小怎么计算对象头是什么东西&quot;&gt;Java对象由什么组成？java对象的内存大小怎么计算？对象头是什么东西？&lt;/h1&gt;

&lt;p&gt;首先来看一下对象的创建流程：
&lt;img src=&quot;/images/posts/java/clipboard.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;1类加载检查&quot;&gt;1.类加载检查&lt;/h2&gt;
&lt;p&gt;      虚拟机遇到一条new指令时，首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用，并且检查这个 符号引用代表的类是否已被加载、解析和初始化过。如果没有，那必须先执行相应的类加载过程。 &lt;br /&gt;
  new指令对应到语言层面上讲是，new关键词、对象克隆、对象序列化等。&lt;/p&gt;

&lt;h2 id=&quot;2分配内存&quot;&gt;2.分配内存&lt;/h2&gt;
&lt;p&gt;      在类加载检查通过后，接下来虚拟机将为新生对象分配内存。对象所需内存的大小在类 加载完成后便可完全确定，为 对象分配空间的任务等同于把 一块确定大小的内存从Java堆中划分出来。&lt;/p&gt;

&lt;h3 id=&quot;这个步骤有两个问题&quot;&gt;这个步骤有两个问题：&lt;/h3&gt;
&lt;p&gt;1.如何划分内存。&lt;br /&gt;
2.在并发情况下，可能出现正在给对象A分配内存，指针还没来得及修改，对象B又同时使用了原来的指针来分配内存的 情况。&lt;/p&gt;

&lt;p&gt;划分内存的方法：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;“指针碰撞”（Bump the Pointer）：(默认用指针碰撞) 如果Java堆中内存是绝对规整的，所有用过的内存都放在一边，空闲的内存放在另一边，中间放着一个指针作为分界点 的指示器，那所分配内存就仅仅是把那个指针向空闲空间那边挪动一段与对象大小相等的距离。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;“空闲列表”（Free List）：如果Java堆中的内存并不是规整的，已使用的内存和空 闲的内存相互交错，那就没有办法简单地进行指针碰撞了，虚拟机就必须维护一个列表，记录上哪些内存块是可用的，在分配的时候从列表中找到一块足够大的空间划分给对象实例，  并更新列表上的记录&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;解决并发问题的方法：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;CAS（compare and swap） 虚拟机采用CAS配上失败重试的方式保证更新操作的原子性来对分配内存空间的动作进行同步处理。&lt;/li&gt;
  &lt;li&gt;本地线程分配缓冲（Thread Local Allocation Buffer,TLAB）
把内存分配的动作按照线程划分在不同的空间之中进行，即每个线程在Java堆中预先分配一小块内存。通过­XX:+/UseTLAB参数来设定虚拟机是否使用TLAB(JVM会默认开启­XX:+UseTLAB)，­XX:TLABSize 指定TLAB大小。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;3初始化&quot;&gt;3.初始化&lt;/h2&gt;
&lt;p&gt;     内存分配完成后，虚拟机需要将分配到的内存空间都初始化为零值（不包括对象头）， 如果使用TLAB，这一工作过程也 可以提前至TLAB分配时进行。这一步操作保证了对象的实例字段在Java代码中可以不赋初始值就直接使用，程序能访问 到这些字段的数据类型所对应的零值。&lt;/p&gt;

&lt;h2 id=&quot;4设置对象头&quot;&gt;4.设置对象头&lt;/h2&gt;
&lt;p&gt;     初始化零值之后，虚拟机要对对象进行必要的设置，&lt;strong&gt;&lt;em&gt;例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息&lt;/em&gt;&lt;/strong&gt;。这些信息存放在对象的对象头Object Header之中。
&lt;em&gt;**在HotSpot虚拟机中，对象在内存中存储的布局可以分为3块区域：对象头（Header）、 实例数据（Instance Data） 和对齐填充（Padding）。 **&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;如图：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/java/objecthead.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;对象头&lt;/strong&gt;：
Mark Word：包含一系列的标记位，比如轻量级锁的标记位，偏向锁标记位等等。在32位系统占4字节，在64位系统中占8字节；
Class Pointer(类型指针)：用来指向对象对应的Class对象（其对应的元数据对象）的内存地址。在32位系统占4字节，在64位系统中占8字节（开启JVM指针压缩后占4字节）；
Length：占4个字节（只有数组对象才有）；&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;实例数据&lt;/strong&gt;（Instance Data）：
如对象中定义的属性id，String等，字节长度根据字段类型长度进行累加。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;对齐填充&lt;/strong&gt;：
如果上述两个区域加起来字节数不是8的整数倍，会进行追加填充字节到8的整数倍。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;       比如现在计算一个对象大小，对象中只有一个int成员属性占四个字节，那么在64位操作系统内存中创建一个该对象占几个字节？&lt;br /&gt;
     1.首先计算对象头，成员属性int类型不是数组，对象头没有Length，所以对象头内存大小=8（MarkWork）+8(ClassPointer)=16 byte；&lt;br /&gt;
     2.实例数据大小，即int成员属性大小=4 byte；&lt;br /&gt;
     3.前面两个加起来内存大小=20 byte，不是8的整数倍，所以需要对齐填充至24 byte；&lt;br /&gt;
综上，该对象占内存大小为24字节。&lt;/p&gt;

&lt;p&gt;    HotSpot虚拟机的对象头包括两部分信息，第一部分用于存储对象自身的运行时数据， 如哈 希码（HashCode）、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时 间戳等。对象头的另外一部分 是类型指针，即对象指向它的类元数据的指针，虚拟机通过这个指针来确定这个对象是哪个类的实例。&lt;/p&gt;

&lt;h2 id=&quot;5执行方法&quot;&gt;5.执行&lt;init&gt;方法&lt;/init&gt;&lt;/h2&gt;
&lt;p&gt;      即对象按照程序员的意愿进行初始化。对应到语言层面上讲，就是为属性赋值（注意，这与上面的赋 零值不同，这是由程序员赋的值），和执行构造方法。&lt;/p&gt;</content><author><name>SideWipe</name></author><category term="Java" /><summary type="html">Java对象由什么组成？java对象的内存大小怎么计算？对象头是什么东西？</summary></entry><entry><title type="html">Android Studio 遇到问题集锦</title><link href="https://side-wipe.github.io/2018/12/05/android-studio/" rel="alternate" type="text/html" title="Android Studio 遇到问题集锦" /><published>2018-12-05T00:00:00+08:00</published><updated>2018-12-05T00:00:00+08:00</updated><id>https://side-wipe.github.io/2018/12/05/android-studio</id><content type="html" xml:base="https://side-wipe.github.io/2018/12/05/android-studio/">&lt;h2 id=&quot;打开-android-studio-卡在fetching-android-sdk-component-information界面&quot;&gt;打开 Android Studio 卡在「Fetching Android SDK component information」界面。&lt;/h2&gt;

&lt;p&gt;如图：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/android/android-studio-check-sdk.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Android Studio First Run 检测 Android SDK 及更新，由于众所周知的原因，我们会「Unable to access Android SDK add-on list」，而且大家一般也已经提前配置好了 Android SDK，真正需要更新的时候手动去 SDK Manager 更新就好了。&lt;/p&gt;

&lt;p&gt;解决方案：&lt;/p&gt;

&lt;p&gt;在 Android Studio 安装目录 bin/idea.properties 文件最后追加一句&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;disable.android.first.run=true
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;参考： &lt;a href=&quot;http://ask.android-studio.org/?/article/14&quot;&gt;http://ask.android-studio.org/?/article/14&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;新建工程后构建时提示找不到-appcompat-v7&quot;&gt;新建工程后构建时提示找不到 appcompat-v7&lt;/h2&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Error:Failed to find: com.android.support:appcompat-v7:22.+
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;解决方案：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;打开 SDK Manager，然后安装 Extras 下的 Android Support Repository：&lt;/p&gt;

    &lt;p&gt;&lt;img src=&quot;/images/posts/android/android-support-repository.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Rebuild 工程。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;aidl-文件的放置&quot;&gt;aidl 文件的放置&lt;/h2&gt;

&lt;p&gt;按以前 Eclipse 的方式，将 aidl 及其包目录层级放置在与自己的顶级包同级的目录下，即如下的 android/content/pm：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;app/src/main
├─assets
├─java
│  ├─android
│  │  └─content
│  │      └─pm
│  └─org
│      └─mazhuang
│          └─easycleaner
└─res
   ├─drawable
   ├─layout
   ├─menu
   ...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;然而这样在调用处一直报错：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Cannot resolve symbol 'IPackageStatsObserver'
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;解决方案：&lt;/p&gt;

&lt;p&gt;将 aidl 文件放置在与 app/src/main/java 目录同级的 app/src/main/aidl 文件夹下。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;app/src/main
├─aidl
│  └─android
│      └─content
│          └─pm
├─assets
├─java
│  └─org
│      └─mazhuang
│          └─easycleaner
└─res
   ├─drawable
   ├─layout
   ├─menu
   ...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;在-android-studio-里编译通过之后命令行使用-gradlew-build-为什么还是会重新下载-gradle&quot;&gt;在 Android Studio 里编译通过之后，命令行使用 gradlew build 为什么还是会重新下载 Gradle？&lt;/h2&gt;

&lt;p&gt;Gradle 的版本在 Android Studio 工程里有三处：&lt;/p&gt;

&lt;p&gt;一、gradle/wrapper/gradle-wrapper.properties 文件的 distributionUrl 字段里指定的。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;#Wed Oct 21 11:34:03 PDT 2015
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.8-all.zip
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;比如这里指定的是 2.8 版本。&lt;/p&gt;

&lt;p&gt;二、Android Studio 的 File &amp;gt; Project Structure &amp;gt; Project 里显示的。&lt;/p&gt;

&lt;p&gt;这个实际上就是显示的「一」里的版本。&lt;/p&gt;

&lt;p&gt;三、Android Studio 的 File &amp;gt; Settings &amp;gt; Build, Execution, Deployment &amp;gt; Build Tools &amp;gt; Gradle 里选择的是「Use default gradle wrapper (recommended)」还是「Use local gradle distribution」。&lt;/p&gt;

&lt;p&gt;出现题目里的问题一般是由于「三」中选择的是「Use local gradle distribution」，这个选项下的「Gradle home」路径一般是指向 Android Studio 安装目录下的 Gradle 目录，比如 C:/Program Files/Android/Android Studio/gradle/gradle-2.8，而 gradlew 脚本是独立于 Android Studio 的，所以并不受其配置的影响，它是使用「一」里指定的版本，会到 ~/.gradle/wrapper/dists 目录下去寻找对应版本的 Gradle 是否已经存在，如果没有话就会去重新下载。&lt;/p&gt;

&lt;h2 id=&quot;模拟器启动失败&quot;&gt;模拟器启动失败&lt;/h2&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;PANIC: ANDROID_SDK_HOME is defined but could not find Nexus_5_API_23.ini file in $ANDROID_SDK_HOME/.android/avd
(Note: avd is searched in the order of $ANDROID_AVD_HOME,$ANDROID_SDK_HOME/.android/avd and $HOME/.android/avd)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;实际上文件存在于 $HOME/.android/avd 目录下，但看样子如果设置了 $ANDROID_SDK_HOME 环境变量，Android Studio 在 $ANDROID_SDK_HOME/.android/avd 下找不到模拟器文件将直接报错，而不会再去找 $HOME 目录下的文件。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;解决方案：&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;添加 $ANDROID_AVD_HOME 环境变量，值为 $HOME/.android/avd 的展开全路径。&lt;/p&gt;

&lt;h2 id=&quot;debugkeystore-的存放位置&quot;&gt;debug.keystore 的存放位置&lt;/h2&gt;

&lt;p&gt;在使用高德地图 SDK 时，需要 key 与 keystore 文件的 sha1 校验通过，而我将 debug.keystore 拷贝到 $HOME/.android 目录下后发现一直提示 key 校验失败，也就是没有使用我拷贝到 $HOME/.android 目录下的 debug.keystore 来做 debug 签名。&lt;/p&gt;

&lt;p&gt;原因是 debug.keystore 的默认存储路径是 $HOME/.android，但是如果配置了 $ANDROID_SDK_HOME，则会将 debug.keystore $ANDROID_SDK_HOME/.android 目录下。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;解决方案：&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;将 debug.keystore 文件拷贝到 $ANDROID_SDK_HOME/.android 目录下。&lt;/p&gt;

&lt;p&gt;BTW:&lt;/p&gt;

&lt;p&gt;关于给 App 签名的手动、自动方法参考 &lt;a href=&quot;http://developer.android.com/tools/publishing/app-signing.html&quot;&gt;Signing Your Applications&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;Android Studio 自动生成的 debug.keystore 的信息：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Keystore password: android&lt;/li&gt;
  &lt;li&gt;Key alias: androiddebugkey&lt;/li&gt;
  &lt;li&gt;Key password: android&lt;/li&gt;
&lt;/ul&gt;</content><author><name>SideWipe</name></author><category term="Android" /><summary type="html">打开 Android Studio 卡在「Fetching Android SDK component information」界面。</summary></entry></feed>