首页 文章 精选 留言 我的

精选列表

搜索[智能解析],共10000篇文章
优秀的个人博客,低调大师

dom4j解析xml文件简单版

1. 获取xml信息 /** * 获取xml信息 * @param filename 文件相对路径 */ public static Document load(String filename) { Document document = null; try { SAXReader saxReader = new SAXReader(); //使用spring的文件读取方法:org.springframework.core.io.Resource Resource resource = new ClassPathResource(filename); document = saxReader.read(resource.getFile()); // 读取XML文件,获得document对象 } catch (Exception ex) { ex.printStackTrace(); } return document; } reader的read方法是重载的,可以从InputStream, File, Url等多种不同的源来读取。得到的Document对象就代表了整个XML。 读取的字符编码是按照XML文件头定义的编码来转换。如果遇到乱码问题,注意要把各处的编码名称保持一致即可 获取根节点 root元素是xml文档的根节点。一切XML分析都是从Root元素开始的 #获取xml文档 Document document = load("conf/test.xml"); #获取xml的根节点 Element root = document.getRootElement(); 2. 常用到的方法 #获得根元素下的所有子节点。 List<Element> elementList=root.elements(); #获得element节点的名字 String elementName=element.getName(); #获得element节点的文本节点值 String elementValue=element.getText(); #获得element节点的属性 Attribute attribute=element.attribute(); #获得属性值 String attrValue=element.attributeValue("attrName"); 3. xml遍历 xml节点的遍历有如下方式: //方式一: 枚举所有子节点 for ( Iterator i = root.elementIterator(); i.hasNext(); ) { Element element = (Element) i.next(); //TODO 获取相关节点信息和操作 } //方式二: 枚举名称为foo的节点 for ( Iterator i = root.elementIterator(foo); i.hasNext();) { Element foo = (Element) i.next(); //TODO 获取相关节点信息和操作 } //方式三 枚举属性 for ( Iterator i = root.attributeIterator(); i.hasNext(); ) { Attribute attribute = (Attribute) i.next(); //TODO 获取相关节点信息和操作 } //方式四: 循环遍历子节点 List<Element> elementList=root.elements(); for(int i=0;i<elementList.size(); i++){ Element element = (Element) elementList.get(i); //TODO 获取相关节点信息和操作 //子节点遍历 List elementRoot = element.elements(); //判断是否存在子节点 if(null!=elementRoot && elementRoot.size()>0){ //递归调用此方法 //TODO 相关操作 } }

优秀的个人博客,低调大师

Spring Cloud 上手实战-架构解析及实作

Spring简介 为什么要使用微服务 单体应用: 目前为止绝大部分的web应用软件采用单体应用,所有的应用的用户UI、业务逻辑、数据库访问都打包在一个应用程序上。 缺点: 开发相互干扰,随着应用的不断升级沟通协调成本增加 应用上线由于某个功能升级导致需要整体的构建、整体测试、整体发布 微服务 把单体应用拆分成小的、松藕合分布式服务的形式 每个应用一定是独立构建、独立部署与测试,应用也是独立发布,应用于应用直接通常通过restful API接口的形式进行相互调用。解决了单体应用带来的困扰。 Spring cloud 是什么 发展历史 2002,Rod Johonson发表了<<Expert One-on-One J2EE Design and Development>>, 包含了3万行的代码在包com.interface21中 2003,Juerge Hoeller,Yann Caroff 联系Rod,将书中代码开源,Yann提出Spring这个词,冠于书中代码; 并发布0.9,使用Apache 2.0协议;Thomas Risberg负责Spring JDBC;Ben Alex将Acegi Security贡献给Rod和Juergen 2004,1.0发布 2005,<<Professional Java Development with Spring Framework>> <<Pro Spring>>出版;1.2.6发布。 AspectJ Leader Adrian Coyler加入Interface21作为首席科学家; 2006,Security 1.0、Spring webflow 1.0发布;Spring 2.0发布; 2007,Spring Batch、WebService、Integration发布;Spring 2.5发布; 2008,Spring Integration 1.0,Spring 2.5.6,Spring Batch 1.0;买了g2One,一家提供Groovy and Grails的公司; 2009,被VMWare发了42亿美金买下;Spring Python、STS发布、3.0发布(将包拆开,不提供超级包),买了Cloud Foundry; 2010,VMWare买了RabbitMQ公司,获得RabbitMQ和Redis技术; 2011,Spring 3.1、Spring AMQP、Spring Data JPA、Spring-data-common 1.0 、Spring Data Redis、Spring Data Mongodb发布; 2012,Rod Johnson离开VMWare;Spring Android、Mobile发布; 2013,VMWare 和 EMC 合力组建了一家公司,Pivotal。Spring 4.0、Spring Boot发布; 2014,Spring 4.1.3、SpringBoot 1.0发布; 2015,Spring 4.2、4.3发布; 2016,Spring 4.3 GA 2017,Spirng 5.x Spring的出现让EJB等重量级的容器技术逐渐走向末路。Spring 通过对Bean的生命周期的管理,可以快速方便的实现业务的逻辑处理。Spring 可以方便的整合几乎所有的主流的开源项目如JPA,缓存,消息组合等等,方便的进行开发。 Spring Cloud实战 本实战目的:全程演示如何创建一个基础的、可用的Spring cloud分布式应用系统演示Spring Cloud各部分组件如何在应用之前协调、调用。了解整个Spring Cloud的项目基本情况,有一个初步的认识。本实战JAVA采用JDK8Spring Boot版本采用最新2.1.0 release.Spring Cloud版本采用Greenwich.M1。 2.2.1. Spring Cloud基础搭建 使用 阿里云的spring cloud 架构图在线设计地址: https://www.freedgo.com 即可使用Freedgo Desing 进行图形设计. 架构预览地址: https://www.freedgo.com/draw_index.html?#Uhttps://www.freedgo.com/templates/network/springcloud_aliyun.xml Freedgo Design 是一in款在线绘制专业图形的网站。Freedgo Design可以绘制各种类型的图形,针对业务逻辑的流程图,软件设计ER模板,工作流,各种云平台的系统部署架构图包括阿里云、AWS云、腾讯云、Oracle、Asure云、IBM云平台等。 2.2.1.1. 建立基础代码 开发工具:Intellij idea1、 建立一个mvn 工程项目使用的java jdk 采用jdk8 2.2.1.2. 服务发现 建立子模块discovery-service 我们可以看到mvn 依赖导入了netflix-eureka-serverSpring boot 会启动服务发现服务 <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency> 2.2.1.3. 服务配置 建立子模块config-service <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-server</artifactId> </dependency> 2.2.1.4. 服务路由 服务路由采用 Netflix Zuul 通过服务路由作为eureka client,可以被发现服务监控 <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-zuul</artifactId> </dependency> 基础的组件创建完毕 2.2.1.5. 用户认证中心 2.2.1.6. 业务服务模块 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-oauth2</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-security</artifactId> </dependency> 2.2.2. 服务发现 传统服务发现方式通过网络DNS和负载均衡设备实现,需要网络和硬件设置支持,维护成本高,网络环境复杂居于云的服务发现具有如下优点 高可用,热部署 负载均衡 健康检查,容错机制 抽象服务的逻辑名称 使用Spring与Netflix Eureka实现服务发现 实现服务发现功能需要配置服务发现端及需要注册服务配置客户端1、 发现服务端配置服务注册需要30 s 的时间才能显示在 Eureka 服务中,因为 Eureka 需要从服务接收3次连续心跳包 ping,每次心跳包 ping 间隔10 s,然后才能使用这个服务。在部署和测试服务时,要牢记这一点。application.yml server: port: 8761 # Eureka 服务器将要监听的端口 eureka: client: registerWithEureka: false #不要使用 Eureka 服务进行注册 fetchRegistry: false #不要在本地缓存注册表信息 使用一个新的注解@EnableEurekaServer,就可以让我们的服务成为一个 Eureka 服务 2、 服务发现客户端配置 以config-service为例 需要做2件事情1、 成为服务发现的客户端 <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> 2、 配置application.yml(对应config-server来说我们只需要配置如下) spring: cloud: config: discovery: enabled: true 启动运行查看 http://localhost:8761/eureka/apps/config-service。 应用作为服务发现的客户端设置 1、 添加客户端依赖 <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> 2、 配置application.yml spring: application: name: business_service eureka: instance: preferIpAddress: true #注册服务的 IP,而不是服务器名称 client: registerWithEureka: true #向 Eureka 注册服务 fetchRegistry: true serviceUrl: #拉取注册表的本地副本 defaultZone: http://localhost:8761/eureka/ #Eureka 服务的位置 同样通过mvn spring-boot:run 打开http://localhost:8761 ,business_service已经注册成功 使用服务发现查找服务 2.2.3. 用户认证中心 OAuth2协议说明: 整体OAuth协议包括两方面:1、 访问授权:用户必须通过授权获取令牌2、 资源权限:通过授权的用户访问受保护的资源,根据定义访问权限来决定是否可以访问资源配置说明: 启用OAuth授权服务 增加@EnableAuthorizationServer 用于告诉 Spring Cloud,该服务将作为 OAuth2 服务 package com.yuaoq.train.business; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer; @SpringBootApplication @EnableResourceServer @EnableAuthorizationServer public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } } OAuth访问授权配置,配置注册的客户端应用程序 @Configuration public class Auth2Config extends AuthorizationServerConfigurerAdapter { @Autowired private AuthenticationManager authenticationManager; @Autowired private UserDetailsService userDetailsService; // 覆盖 configure()方法。这定义了哪些客户端将注册到服务 @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.inMemory() .withClient("yuaoq") .secret("{noop}secret") .authorizedGrantTypes( "refresh_token", "password", "client_credentials") .scopes("webclient","mobileclient"); } // 该方法定义了 AuthenticationServerConfigurer 中使用的不同组件。这段代码告诉 Spring 使用 Spring 提供的默认验证管理器和用户详细信息服务 @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints .authenticationManager(authenticationManager) .userDetailsService(userDetailsService); } } 配置用户权限 @Configuration public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter { @Override @Bean // AuthenticationManagerBean 被 Spring Security 用来处理验证 public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } // Security 使用 UserDetailsService 处理返回的用户信息,这些用户信息将由 Spring Security 返回 @Override @Bean public UserDetailsService userDetailsServiceBean() throws Exception { return super.userDetailsServiceBean(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { // configure()方法是定义用户、密码和角色的地方 auth.inMemoryAuthentication() .withUser("admin") .password("{noop}password") .roles("ADMIN","USER") .and() .withUser("anyone") .password("{noop}password") .roles("USER"); } } 获取用户信息(提供给其他服务获取用户信息使用) @GetMapping(value = "/auth/user") public Map<String, Object> user(OAuth2Authentication user) { Map<String, Object> userInfo = new HashMap<>(); userInfo.put("user", user.getUserAuthentication().getPrincipal()); userInfo.put("authorities", AuthorityUtils.authorityListToSet(user.getUserAuthentication().getAuthorities())); return userInfo; } 通过postman测试自此提供了一个用户认证的微服务模块. 2.2.3.1. 业务模块微服务 1、 对外提供restful Api@RestController:由spring web提供的居于restful 的接口标签生成一个restful api @PostMapping("/list") public ResponseEntity<List<String>> getBusiness() throws Exception { List<String> list = new ArrayList<String>(); list.add("a"); list.add("b"); return Optional.of(list) .map(a -> new ResponseEntity<List<String>>(a, HttpStatus.OK)) .orElseThrow(() -> new Exception("error")); } 使用postman调用接口 从postman返回的结果可以看到401,未授权。因为business_service服务引入了spring-cloud-starter-security 那么默认是会对所有访问做安全控制。 2、 服务的授权保护现在business/list 是未授权,那怎么配置一个受保护的oauth2.0资源,通过如下步骤 设置服务是一个受oauth保护的资源 定义应用的OAuth属性定义回调 URL security: oauth2: resource: user-info-uri: http://localhost:8282/auth/user 定义授权用户可以访问 @Configuration public class ResourceServerConfig extends ResourceServerConfigurerAdapter { // antMatchers()允许开发人员限制对受保护的 URL 和 HTTP DELETE 动词的调用 // hasRole()方法是一个允许访问的角色列表,该列表由逗号分隔 @Override public void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers(HttpMethod.POST, "/api/v1/business/**") .hasRole("ADMIN") .anyRequest() .authenticated(); } } 该段代码说明具有ADMIN角色的用户可以访问/api/v1/business/ 下的所有的POST 请求验证如下: 至此通过OAuth2.0保护微服务的基本做法已经完成。 2.2.4. 服务路由网关服务网关:服务客户端不再直接调用服务。取而代之的是,服务网关作为单个策略执行点(Policy Enforcement Point,PEP),所有调用都通过服务网关进行路由,然后被路由到最终目的地。@EnabeZuulServer使用此注解将创建一个 Zuul 服务器,它不会加载任何 Zuul 反向代理过滤器,也不会使用 Netflix Eureka 进行服务发现. 成为一个服务网关步骤:1、 添加@EnableZuulProxy2、 在application.yml添加route 规则 zuul: sensitive-headers: set-cookies routes: business_service: /busi/** 通过postman测试如下:

优秀的个人博客,低调大师

客户端负载均衡Ribbon之源码解析

文章首发于微信公众《程序员果果》地址:https://mp.weixin.qq.com/s/seYEMxztE2ZqfmaI8pO-7w 什么是负载均衡器? 假设有一个分布式系统,该系统由在不同计算机上运行的许多服务组成。但是,当用户数量很大时,通常会为服务创建多个副本。每个副本都在另一台计算机上运行。此时,出现 “Load Balancer(负载均衡器)”。它有助于在服务器之间平均分配传入流量。 服务器端负载均衡器 传统上,Load Balancers(例如Nginx、F5)是放置在服务器端的组件。当请求来自 客户端 时,它们将转到负载均衡器,负载均衡器将为请求指定 服务器。负载均衡器使用的最简单的算法是随机指定。在这种情况下,大多数负载平衡器是用于控制负载平衡的硬件集成软件。 重点: 对客户端不透明,客户端不知道服务器端的服务列表,甚至不知道自己发送请求的目标地址存在负载均衡器。 服务器端维护负载均衡服务器,控制负载均衡策略和算法。 客户端负载均衡器 当负载均衡器位于 客户端 时,客户端得到可用的服务器列表然后按照特定的负载均衡策略,分发请求到不同的 服务器 。 重点: 对客户端透明,客户端需要知道服务器端的服务列表,需要自行决定请求要发送的目标地址。 客户端维护负载均衡服务器,控制负载均衡策略和算法。 目前单独提供的客户端实现比较少( 我用过的只有Ribbon),大部分都是在框架内部自行实现。 Ribbon 简介 Ribbon是Netflix公司开源的一个客户单负载均衡的项目,可以自动与 Eureka 进行交互。它提供下列特性: 负载均衡 容错 以异步和反应式模型执行多协议 (HTTP, TCP, UDP) 缓存和批量 Ribbon中的关键组件 ServerList:可以响应客户端的特定服务的服务器列表。 ServerListFilter:可以动态获得的具有所需特征的候选服务器列表的过滤器。 ServerListUpdater:用于执行动态服务器列表更新。 Rule:负载均衡策略,用于确定从服务器列表返回哪个服务器。 Ping:客户端用于快速检查服务器当时是否处于活动状态。 LoadBalancer:负载均衡器,负责负载均衡调度的管理。 源码分析 LoadBalancerClient 实际应用中,通常将 RestTemplate 和 Ribbon 结合使用,例如: @Configuration public class RibbonConfig { @Bean @LoadBalanced RestTemplate restTemplate() { return new RestTemplate(); } } 消费者调用服务接口: @Service public class RibbonService { @Autowired private RestTemplate restTemplate; public String hi(String name) { return restTemplate.getForObject("http://service-hi/hi?name="+name,String.class); } } @LoadBalanced,通过源码可以发现这是一个标记注解: /** * Annotation to mark a RestTemplate bean to be configured to use a LoadBalancerClient * @author Spencer Gibb */ @Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @Qualifier public @interface LoadBalanced { } 通过注释可以知道@LoadBalanced注解是用来给RestTemplate做标记,方便我们对RestTemplate添加一个LoadBalancerClient,以实现客户端负载均衡。 根据spring boot的自动配置原理,可以知道同包下的LoadBalancerAutoConfiguration,应该是实现客户端负载均衡器的自动化配置类。代码如下: @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 loadBalancedRestTemplateInitializerDeprecated( final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) { return () -> restTemplateCustomizers.ifAvailable(customizers -> { for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) { for (RestTemplateCustomizer customizer : customizers) { customizer.customize(restTemplate); } } }); } @Autowired(required = false) private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList(); @Bean @ConditionalOnMissingBean public LoadBalancerRequestFactory loadBalancerRequestFactory( LoadBalancerClient loadBalancerClient) { return new LoadBalancerRequestFactory(loadBalancerClient, transformers); } @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 restTemplate -> { List<ClientHttpRequestInterceptor> list = new ArrayList<>( restTemplate.getInterceptors()); list.add(loadBalancerInterceptor); restTemplate.setInterceptors(list); }; } } @Configuration @ConditionalOnClass(RetryTemplate.class) public static class RetryAutoConfiguration { @Bean @ConditionalOnMissingBean public LoadBalancedRetryFactory loadBalancedRetryFactory() { return new LoadBalancedRetryFactory() {}; } } @Configuration @ConditionalOnClass(RetryTemplate.class) public static class RetryInterceptorAutoConfiguration { @Bean @ConditionalOnMissingBean public RetryLoadBalancerInterceptor ribbonInterceptor( LoadBalancerClient loadBalancerClient, LoadBalancerRetryProperties properties, LoadBalancerRequestFactory requestFactory, LoadBalancedRetryFactory loadBalancedRetryFactory) { return new RetryLoadBalancerInterceptor(loadBalancerClient, properties, requestFactory, loadBalancedRetryFactory); } @Bean @ConditionalOnMissingBean public RestTemplateCustomizer restTemplateCustomizer( final RetryLoadBalancerInterceptor loadBalancerInterceptor) { return restTemplate -> { List<ClientHttpRequestInterceptor> list = new ArrayList<>( restTemplate.getInterceptors()); list.add(loadBalancerInterceptor); restTemplate.setInterceptors(list); }; } } } 从代码可以看出,这个类作用主要是使用RestTemplateCustomizer对所有标注了@LoadBalanced的RestTemplate Bean添加了一个LoadBalancerInterceptor拦截器,而这个拦截器的作用就是对请求的URI进行转换获取到具体应该请求哪个服务实例。 那再看看添加的拦截器LoadBalancerInterceptor的代码,如下: public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor { private LoadBalancerClient loadBalancer; private LoadBalancerRequestFactory requestFactory; public LoadBalancerInterceptor(LoadBalancerClient loadBalancer, LoadBalancerRequestFactory requestFactory) { this.loadBalancer = loadBalancer; this.requestFactory = requestFactory; } public LoadBalancerInterceptor(LoadBalancerClient loadBalancer) { // for backwards compatibility this(loadBalancer, new LoadBalancerRequestFactory(loadBalancer)); } @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)); } } 从代码可以看出 LoadBalancerInterceptor 拦截了请求后,通过LoadBalancerClient执行具体的请求发送。 打开LoadBalancerClient,发现它是一个接口: public interface LoadBalancerClient { ServiceInstance choose(String serviceId); <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); } 接口说明: ServiceInstance choose(String serviceId):根据传入的服务id,从负载均衡器中为指定的服务选择一个服务实例。 T execute(String serviceId, LoadBalancerRequest request):根据传入的服务id,指定的负载均衡器中的服务实例执行请求。 T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest request):根据传入的服务实例,执行请求。 LoadBalancerClient 有一个唯一的实现类 RibbonLoadBalancerClient,关键代码如下: public class RibbonLoadBalancerClient implements LoadBalancerClient { public ServiceInstance choose(String serviceId) { Server server = this.getServer(serviceId); return server == null ? null : new RibbonLoadBalancerClient.RibbonServer(serviceId, server, this.isSecure(server, serviceId), this.serverIntrospector(serviceId).getMetadata(server)); } public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException { ILoadBalancer loadBalancer = this.getLoadBalancer(serviceId); Server server = this.getServer(loadBalancer); if (server == null) { throw new IllegalStateException("No instances available for " + serviceId); } else { RibbonLoadBalancerClient.RibbonServer ribbonServer = new RibbonLoadBalancerClient.RibbonServer(serviceId, server, this.isSecure(server, serviceId), this.serverIntrospector(serviceId).getMetadata(server)); return this.execute(serviceId, ribbonServer, request); } } protected Server getServer(String serviceId) { return this.getServer(this.getLoadBalancer(serviceId)); } protected Server getServer(ILoadBalancer loadBalancer) { return loadBalancer == null ? null : loadBalancer.chooseServer("default"); } protected ILoadBalancer getLoadBalancer(String serviceId) { return this.clientFactory.getLoadBalancer(serviceId); } //省略... } 负载均衡器 从 RibbonLoadBalancerClient 代码可以看出,实际负载均衡的是通过 ILoadBalancer 来实现的。 ILoadBalancer 接口代码如下: 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:向负载均衡器中添加一个服务实例集合。 chooseServer:跟据key,从负载均衡器获取服务实例。 markServerDown:用来标记某个服务实例下线。 getReachableServers:获取可用的服务实例集合。 getAllServers():获取所有服务实例集合,包括下线的服务实例。 ILoadBalancer 的实现 依赖关系示意图如下: NoOpLoadBalancer:啥都不做 BaseLoadBalancer: 一个负载均衡器的基本实现,其中有一个任意列表,可以将服务器设置为服务器池。 可以设置一个ping来确定服务器的活力。 在内部,该类维护一个“all”服务器列表,以及一个“up”服务器列表,并根据调用者的要求使用它们。 DynamicServerListLoadBalancer: 通过动态的获取服务器的候选列表的负载平衡器。 可以通过筛选标准来传递服务器列表,以过滤不符合所需条件的服务器。 ZoneAwareLoadBalancer: 用于测量区域条件的关键指标是平均活动请求,它根据每个rest客户机和每个区域聚合。这是区域内未完成的请求总数除以可用目标实例的数量(不包括断路器跳闸实例)。当在坏区上缓慢发生超时时,此度量非常有效。 该负载均衡器将计算并检查所有可用区域的区域状态。如果任何区域的平均活动请求已达到配置的阈值,则该区域将从活动服务器列表中删除。如果超过一个区域达到阈值,则将删除每个服务器上活动请求最多的区域。一旦去掉最坏的区域,将在其余区域中选择一个区域,其概率与其实例数成正比。服务器将使用给定的规则从所选区域返回。对于每个请求,将重复上述步骤。也就是说,每个与区域相关的负载平衡决策都是实时做出的,最新的统计数据可以帮助进行选择。 那么在整合Ribbon的时候Spring Cloud默认采用了哪个具体实现呢?我们通过RibbonClientConfiguration配置类,可以知道在整合时默认采用了ZoneAwareLoadBalancer来实现负载均衡器。 @Bean @ConditionalOnMissingBean public ILoadBalancer ribbonLoadBalancer(IClientConfig config, ServerList<Server> serverList, ServerListFilter<Server> serverListFilter, IRule rule, IPing ping, ServerListUpdater serverListUpdater) { return (ILoadBalancer)(this.propertiesFactory .isSet(ILoadBalancer.class, this.name) ? (ILoadBalancer)this.propertiesFactory .get(ILoadBalancer.class, config, this.name) : new ZoneAwareLoadBalancer(config, rule, ping, serverList, serverListFilter, serverListUpdater)); } 从这段代码 ,也可以看出,负载均衡器所需的主要配置项是IClientConfig, ServerList, ServerListFilter, IRule, IPing, ServerListUpdater。下面逐一分析他们。 IClientConfig IClientConfig 用于对客户端或者负载均衡的配置,它的默认实现类为 DefaultClientConfigImpl。 IRule 为LoadBalancer定义“负载均衡策略”的接口。 public interface IRule{ public Server choose(Object key); public void setLoadBalancer(ILoadBalancer lb); public ILoadBalancer getLoadBalancer(); } IRule 的实现 依赖关系示意图如下: BestAvailableRule:选择具有最低并发请求的服务器。 ClientConfigEnabledRoundRobinRule:轮询。 RandomRule:随机选择一个服务器。 RoundRobinRule:轮询选择服务器。 RetryRule:具备重试机制的轮询。 WeightedResponseTimeRule:根据使用平均响应时间去分配一个weight(权重) ,weight越低,被选择的可能性就越低。 ZoneAvoidanceRule:根据区域和可用性筛选,再轮询选择服务器。 IPing 定义如何 “ping” 服务器以检查其是否存活。 public interface IPing { public boolean isAlive(Server server); } IPing 的实现 依赖关系示意图如下: PingUrl:真实的去ping 某个url,判断其是否alive。 PingConstant:固定返回某服务是否可用,默认返回true,即可用 NoOpPing:不去ping,直接返回true,即可用。 DummyPing:继承抽象类AbstractLoadBalancerPing,认为所以服务都是存活状态,返回true,即可用。 NIWSDiscoveryPing:结合eureka使用时,如果Discovery Client在线,则认为心跳检测通过。 ServerList 定义获取所有的服务实例清单。 public interface ServerList<T extends Server> { public List<T> getInitialListOfServers(); public List<T> getUpdatedListOfServers(); } ServerList 的实现 依赖关系示意图如下: DomainExtractingServerList:代理类,根据传入的ServerList的值,实现具体的逻辑。 ConfigurationBasedServerList:从配置文件中加载服务器列表。 DiscoveryEnabledNIWSServerList:从Eureka注册中心中获取服务器列表。 StaticServerList:通过静态配置来维护服务器列表。 ServerListFilter 允许根据过滤配置动态获得的具有所需特性的候选服务器列表。 public interface ServerListFilter<T extends Server> { public List<T> getFilteredListOfServers(List<T> servers); } ServerListFilter 的实现 依赖关系示意图如下: DefaultNIWSServerListFilter:完全继承自ZoneAffinityServerListFilter。 ZonePreferenceServerListFilter:EnableZoneAffinity 或 EnableZoneExclusivity 开启状态使用,默认关闭。处理基于区域感知的过滤服务器,过滤掉不和客户端在相同zone的服务,若不存在相同zone,则不进行过滤。 ServerListSubsetFilter:服务器列表筛选器,它将负载平衡器使用的服务器数量限制为所有服务器的子集。如果服务器机群很大(例如数百个),并且不需要使用每一个机群并将连接保存在http客户机的连接池中,那么这是非常有用的。它还可以通过比较总的网络故障和并发连接来驱逐相对不健康的服务器。 ServerListUpdater 用于执行动态服务器列表更新。 public interface ServerListUpdater { public interface UpdateAction { void doUpdate(); } void start(UpdateAction updateAction); void stop(); String getLastUpdate(); long getDurationSinceLastUpdateMs(); int getNumberMissedCycles(); int getCoreThreads(); } ServerListUpdater 的实现 依赖关系示意图如下: PollingServerListUpdater:默认的实现策略,会启动一个定时线程池,定时执行更新策略。 EurekaNotificationServerListUpdater:利用Eureka的事件监听器来驱动服务列表的更新操作。 参考资料 https://github.com/Netflix/ribbon/wiki http://tech.lede.com/2018/01/11/rd/server/NetflixRibbon/ http://blog.didispace.com/springcloud-sourcecode-ribbon/ https://www.fangzhipeng.com/springcloud/2017/08/11/Ribbon-resources.html https://blog.csdn.net/Tincox/article/details/79210309

优秀的个人博客,低调大师

Python面向对象设计和面向对象编程解析

我们都知道Python是一门灵活度很高的语言,它可以面向过程,面向对象,那我们今天说说Python中的面向对象设计和面向对象编程的区别 面向对象设计和面向对象编程都提到了“面向对象”, 那么首先我们要搞清楚什么是对象 对象:我们可以简单的理解为我们见到的每个实际存在的事物就是一个对象,如果一个人,一座房子,一只小猫等。 那么我们想想,我们怎么面向对象,那就是以对象为中心,去描述这个对象,这个对象有什么特点,什么属性,什么功能等,想想假如你要去向别人描述一个丢失的小猫(一个对象),你要怎么描述呢?小够的品种,大小,毛色,等等。 当你把着一些特征或者属性描述清楚之后,一个对象就被定义下来了。 知道对象的概念之后,我们进入主题 1.面向对象设计 说到设计,我们可以理解为是一种想法,思路,就是把对象的属性,功能(python里用函数表达)糅合在一起形

优秀的个人博客,低调大师

深度解析RocketMQ消息发送的高可用设计

微信公众号「后端进阶」,专注后端技术分享:Java、Golang、WEB框架、分布式中间件、服务治理等等。 老司机倾囊相授,带你一路进阶,来不及解释了快上车! 从rocketmq topic的创建机制可知,一个topic对应有多个消息队列,那么我们在发送消息时,是如何选择消息队列进行发送的?假如这时有broker宕机了,rocketmq是如何规避故障broker的?看完这篇文章,相信你会从文中找到答案。 rocketmq在发送消息时,由于nameserver检测broker是否还存活是有延迟的,在选择消息队列时难免会遇到已经宕机的broker,又或者因为网络原因发送失败的,因此rocketmq采取了一些高可用设计的方案,主要通过两个手段:重试与Broker规避。 重试机制 直接定位到client端发送消息的方法: org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl#sendDefaultImpl: int timesTotal = communicationMode == CommunicationMode.SYNC ? 1 + this.defaultMQProducer.getRetryTimesWhenSendFailed() : 1; for (; times < timesTotal; times++) { // ... } 在client端,发送消息的方式有:同步(SYNC)、异步(ASYNC)、单向(ONEWAY)。 那么可以知道,retryTimesWhenSendFailed决定同步方法重试次数,默认重试次数为3次。 重试机制提高了消息发送的成功率。 选择队列的方式 我在这里提出一个问题: 现在有个由两个broker节点组成的集群,有topic1,默认在每个broker上创建4个队列,分别是:master-a(q0,q1,q2,q3)、master-b(q0,q1,q2,q3),上一次发送消息到master-a的q0队列,此时master-a宕机了,如果继续发送topic1消息,rocketmq如果避免再次发送到master-a? 以上问题引出了rocketmq发送消息时如何选择队列的一些机制,选择队列有两种方式,通过sendLatencyFaultEnable的值来控制,默认值为false,不启动broker故障延迟机制,值为true时启用broker故障延迟机制。 默认机制 sendLatencyFaultEnable=false,消息发送选择队列调用以下方法: org.apache.rocketmq.client.impl.producer.TopicPublishInfo#selectOneMessageQueue: public MessageQueue selectOneMessageQueue(final String lastBrokerName) { if (lastBrokerName == null) { return selectOneMessageQueue(); } else { int index = this.sendWhichQueue.getAndIncrement(); for (int i = 0; i < this.messageQueueList.size(); i++) { int pos = Math.abs(index++) % this.messageQueueList.size(); if (pos < 0) pos = 0; MessageQueue mq = this.messageQueueList.get(pos); if (!mq.getBrokerName().equals(lastBrokerName)) { return mq; } } return selectOneMessageQueue(); } } 这里的lastBrokerName指的是上一次执行消息发送时选择失败的broker,在重试机制下,第一次执行消息发送时,lastBrokerName=null,直接选择以下方法: org.apache.rocketmq.client.impl.producer.TopicPublishInfo#selectOneMessageQueue: public MessageQueue selectOneMessageQueue() { int index = this.sendWhichQueue.getAndIncrement(); int pos = Math.abs(index) % this.messageQueueList.size(); if (pos < 0) pos = 0; return this.messageQueueList.get(pos); } sendWhichQueue是一个利用ThreadLocal本地线程存储自增值的一个类,自增值第一次使用Random类随机取值,此后如果消息发送出发重试机制,那么每次自增取值。 此方法直接用sendWhichQueue自增获取值,再与消息队列的长度进行取模运算,取模目的是为了循环选择消息队列。 如果此时选择的队列发送消息失败了,此时重试机制在再次选择队列时lastBrokerName不为空,回到最开始的那个方法,还是利用sendWhichQueue自增获取值,但这里多了一个步骤,与消息队列的长度进行取模运算后,如果此时选择的队列所在的broker还是上一次选择失败的broker,则继续选择下一个队列。 我们再细想一下,如果此时有broker宕机了,在默认机制下很可能下一次选择的队列还是在已经宕机的broker,没有办法规避故障的broker,因此消息发送很可能会再次失败,重试发送造成了不必要的性能损失。 所以rocketmq采用Broker故障延迟机制来规避故障的broker。 Broker故障延迟机制 sendLatencyFaultEnable=true,消息发送选择队列调用以下方法: org.apache.rocketmq.client.latency.MQFaultStrategy#selectOneMessageQueue: public MessageQueue selectOneMessageQueue(final TopicPublishInfo tpInfo, final String lastBrokerName) { // Broker故障延迟机制 if (this.sendLatencyFaultEnable) { try { // 自增取值 int index = tpInfo.getSendWhichQueue().getAndIncrement(); for (int i = 0; i < tpInfo.getMessageQueueList().size(); i++) { // 队列位置值取模 int pos = Math.abs(index++) % tpInfo.getMessageQueueList().size(); if (pos < 0) pos = 0; MessageQueue mq = tpInfo.getMessageQueueList().get(pos); // 校验队列是否可用 if (latencyFaultTolerance.isAvailable(mq.getBrokerName())) { if (null == lastBrokerName || mq.getBrokerName().equals(lastBrokerName)) return mq; } } // 尝试从失败的broker列表中选择一个可用的broker final String notBestBroker = latencyFaultTolerance.pickOneAtLeast(); int writeQueueNums = tpInfo.getQueueIdByBroker(notBestBroker); if (writeQueueNums > 0) { final MessageQueue mq = tpInfo.selectOneMessageQueue(); if (notBestBroker != null) { mq.setBrokerName(notBestBroker); mq.setQueueId(tpInfo.getSendWhichQueue().getAndIncrement() % writeQueueNums); } return mq; } else { // 从失败条目中移除已经恢复的broker latencyFaultTolerance.remove(notBestBroker); } } catch (Exception e) { log.error("Error occurred when selecting message queue", e); } return tpInfo.selectOneMessageQueue(); } // 默认机制 return tpInfo.selectOneMessageQueue(lastBrokerName); } 该方法利用sendWhichQueue的自增取值的方式轮询选择队列,与默认机制一致,不同的是多了判断是否可用,调用了latencyFaultTolerance.isAvailable(mq.getBrokerName())判断,其中肯定内涵机关,所以我们需要从延迟机制的几个核心类找突破口。 下面我会从源码的角度详细地分析rocketmq是如何实现在一定时间内规避故障broker的。 从发送消息方法源码看出,在发送完消息,会调用updateFaultItem方法: org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl#sendDefaultImpl: // 3.执行真正的消息发送 sendResult = this.sendKernelImpl(msg, mq, communicationMode, sendCallback, topicPublishInfo, timeout - costTime); endTimestamp = System.currentTimeMillis(); this.updateFaultItem(mq.getBrokerName(), endTimestamp - beginTimestampPrev, false); 发送消息时捕捉到异常同样会调用updateFaultItem方法,它是延迟机制的核心方法: endTimestamp = System.currentTimeMillis(); this.updateFaultItem(mq.getBrokerName(), endTimestamp - beginTimestampPrev, true); 其中endTimestamp - beginTimestampPrev等于消息发送需要用到的时间,如果成功发送第三个参数传的是false,发送失败传true,下面继续看updateFaultItem方法的实现源码: org.apache.rocketmq.client.latency.MQFaultStrategy#updateFaultItem: public void updateFaultItem(final String brokerName, final long currentLatency, boolean isolation) { if (this.sendLatencyFaultEnable) { long duration = computeNotAvailableDuration(isolation ? 30000 : currentLatency); this.latencyFaultTolerance.updateFaultItem(brokerName, currentLatency, duration); } } private long computeNotAvailableDuration(final long currentLatency) { for (int i = latencyMax.length - 1; i >= 0; i--) { if (currentLatency >= latencyMax[i]) return this.notAvailableDuration[i]; } return 0; } 其中参数currentLatency为本次消息发送的延迟时间,isolation表示broker是否需要规避,所以消息成功发送表示broker无需规避,消息发送失败时表示broker发生故障了需要规避。 latencyMax和notAvailableDuration是延迟机制算法的核心值,每次发送消息的延迟,它们也决定了失败条目中的startTimestamp的值。 从方法可看出,如果broker需要隔离,消息发送延迟时间默认为30s,再利用这个时间从latencyMax尾部向前找到比currentLatency小的数组下标index,如果没有找到就返回0,我们看看latencyMax和notAvailableDuration这两个数组的默认值: private long[] latencyMax = {50L, 100L, 550L, 1000L, 2000L, 3000L, 15000L}; private long[] notAvailableDuration = {0L, 0L, 30000L, 60000L, 120000L, 180000L, 600000L}; 可看出,如果isolation=true,该broker会得到一个10分钟规避时长,如果isolation=false,那么规避时长就得看消息发送的延迟时间是多少了,我们继续往下撸: org.apache.rocketmq.client.latency.LatencyFaultToleranceImpl#updateFaultItem: public void updateFaultItem(final String name, final long currentLatency, final long notAvailableDuration) { // 从缓存中获取失败条目 FaultItem old = this.faultItemTable.get(name); if (null == old) { // 如果缓存不存在,新建失败条目 final FaultItem faultItem = new FaultItem(name); faultItem.setCurrentLatency(currentLatency); // broker开始可用时间=当前时间+规避时长 faultItem.setStartTimestamp(System.currentTimeMillis() + notAvailableDuration); old = this.faultItemTable.putIfAbsent(name, faultItem); if (old != null) { // 更新旧的失败条目 old.setCurrentLatency(currentLatency); old.setStartTimestamp(System.currentTimeMillis() + notAvailableDuration); } } else { // 更新旧的失败条目 old.setCurrentLatency(currentLatency); old.setStartTimestamp(System.currentTimeMillis() + notAvailableDuration); } } FaultItem为存储故障broker的类,称为失败条目,每个条目存储了broker的名称、消息发送延迟时长、故障规避开始时间。 该方法主要是对失败条目的一些更新操作,如果失败条目已存在,那么更新失败条目,如果失败条目不存在,那么新建失败条目,其中失败条目的startTimestamp为当前系统时间加上规避时长,startTimestamp是判断broker是否可用的时间值: org.apache.rocketmq.client.latency.LatencyFaultToleranceImpl.FaultItem#isAvailable: public boolean isAvailable() { return (System.currentTimeMillis() - startTimestamp) >= 0; } 如果当前系统时间大于故障规避开始时间,说明broker可以继续加入轮询的队伍里了。 写在最后 经过一波源码的分析,我相信你已经找到了你想要的答案,这个故障延迟机制真的是一个很好的设计,我们看源码不仅仅要看爽,爽了之后我们还要思考一下这些优秀的设计思想在平时写代码的过程中是否可以借鉴一下?

优秀的个人博客,低调大师

安卓平台Flutter启动过程全解析

前言 今天主要带大家一起分析下flutter是如何启动、初始化和加载dart代码的。这里有几点需要提前告知: 由于篇幅的问题,关于flutter界面创建、绘制过程将略过; 由于相关的c++代码比较多,而且较为复杂,建议先下载flutter engine的完整开发环境代码,阅读本文更方便; 本文只分析启动过程,参考的项目是基于android studio创建的一个默认flutter项目,以下简称demo。 (文章干货很长 请耐心看完 文末有福利!) 正文 java层启动过程 熟悉android的朋友都知道,一个APP启动会先执行Application再执行Activity(AndroidManifest.xml中配置的启动Activity),结合这个,我们先看看Application里做了什么,在分析过程中我们将挑取一些关键的native方法作为

优秀的个人博客,低调大师

超详细的Guava RateLimiter限流原理解析

限流是保护高并发系统的三把利器之一,另外两个是缓存和降级。限流在很多场景中用来限制并发和请求量,比如说秒杀抢购,保护自身系统和下游系统不被巨型流量冲垮等。 限流的目的是通过对并发访问/请求进行限速或者一个时间窗口内的的请求进行限速来保护系统,一旦达到限制速率则可以拒绝服务或进行流量整形。 常用的限流方式和场景有:限制总并发数(比如数据库连接池、线程池)、限制瞬时并发数(如nginx的limit_conn模块,用来限制瞬时并发连接数,Java的Semaphore也可以实现)、限制时间窗口内的平均速率(如Guava的RateLimiter、nginx的limit_req模块,限制每秒的平均速率);其他还有如限制远程接口调用速率、限制MQ的消费速率。另外还可以根据网络连接数、网络流量、CPU或内存负载等来限流。 比如说,我们需要限制方

优秀的个人博客,低调大师

Tensorflow源码解析1 -- 内核架构和源码结构

1 主流深度学习框架对比 当今的软件开发基本都是分层化和模块化的,应用层开发会基于框架层。比如开发Linux Driver会基于Linux kernel,开发Android app会基于Android Framework。深度学习也不例外,框架层为上层模型开发提供了强大的多语言接口、稳定的运行时、高效的算子,以及完备的通信层和设备层管理层。因此,各大公司早早的就开始了深度学习框架的研发,以便能占领市场。当前的框架有数十种之多,主流的如下(截止到2018年11月) 显然TensorFlow是独一无二的王者。第二名Keras,它是对TensorFlow或Theano接口的二次封装,严格意义上并不是一个独立的深度学习框架。TensorFlow目前也已经集成了Keras,使得安装了TensorFlow的用户就可以直接使用Keras了。 Tens

优秀的个人博客,低调大师

Tensorflow源码解析6 -- TensorFlow本地运行时

1 概述 TensorFlow后端分为四层,运行时层、计算层、通信层、设备层。运行时作为第一层,实现了session管理、graph管理等很多重要的逻辑,是十分关键的一层。根据任务分布的不同,运行时又分为本地运行时和分布式运行时。本地运行时,所有任务运行于本地同一进程内。而分布式运行时,则允许任务运行在不同机器上。 Tensorflow的运行,通过session搭建了前后端沟通的桥梁,前端几乎所有操作都是通过session进行。session的生命周期由创建、运行、关闭、销毁组成,前文已经详细讲述过。可以将session看做TensorFlow运行的载体。而TensorFlow运行的核心对象,则是计算图Graph。它由计算算子和计算数据两部分构成,可以完整描述整个计算内容。Graph的生命周期包括构建和传递、剪枝、分裂、执行等步骤,本

资源下载

更多资源
Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册