首页 文章 精选 留言 我的

精选列表

搜索[SpringCloud],共1289篇文章
优秀的个人博客,低调大师

(十)Java springcloud B2B2C o2o多用户商城 springcloud架构-SSO单点登录之OAuth2.0登录认证(...

之前写了很多关于spring cloud的文章,今天我们对OAuth2.0的整合方式做一下笔记,首先我从网上找了一些关于OAuth2.0的一些基础知识点,帮助大家回顾一下知识点:一、oauth中的角色client:调用资源服务器API的应用 Oauth 2.0 Provider:包括Authorization Server和Resource Server (1)Authorization Server:认证服务器,进行认证和授权 (2)Resource Server:资源服务器,保护受保护的资源user:资源的拥有者 二、下面详细介绍一下Oauth 2.0 ProviderAuthorization Server:(1)AuthorizationEndpoint:进行授权的服务,Default URL:/oauth/authorize (2)TokenEndpoint:获取token的服务,Default URL:/oauth/token Resource Server:OAuth2AuthenticationProcessingFilter:给带有访问令牌的请求加载认证 三、下面再来详细介绍一下Authorization Server:一般情况下,创建两个配置类,一个继承AuthorizationServerConfigurerAdapter,一个继承WebSecurityConfigurerAdapter,再去复写里面的方法。 主要出现的两种注解:1、@EnableAuthorizationServer:声明一个认证服务器,当用此注解后,应用启动后将自动生成几个Endpoint:(注:其实实现一个认证服务器就是这么简单,加一个注解就搞定,当然真正用到生产环境还是要进行一些配置和复写工作的。) /oauth/authorize:验证 /oauth/token:获取token /oauth/confirm_access:用户授权 /oauth/error:认证失败 /oauth/check_token:资源服务器用来校验token /oauth/token_key:如果jwt模式则可以用此来从认证服务器获取公钥 以上这些endpoint都在源码里的endpoint包里面。 2、@Beans:需要实现AuthorizationServerConfigurer AuthorizationServerConfigurer包含三种配置: ClientDetailsServiceConfigurer:client客户端的信息配置,client信息包括:clientId、secret、scope、authorizedGrantTypes、authorities (1)scope:表示权限范围,可选项,用户授权页面时进行选择 (2)authorizedGrantTypes:有四种授权方式 Authorization Code:用验证获取code,再用code去获取token(用的最多的方式,也是最安全的方式)Implicit: 隐式授权模式Client Credentials (用來取得 App Access Token)Resource Owner Password Credentials(3)authorities:授予client的权限 这里的具体实现有多种,in-memory、JdbcClientDetailsService、jwt等。 AuthorizationServerSecurityConfigurer:声明安全约束,哪些允许访问,哪些不允许访问 AuthorizationServerEndpointsConfigurer:声明授权和token的端点以及token的服务的一些配置信息,比如采用什么存储方式、token的有效期等 client的信息的读取:在ClientDetailsServiceConfigurer类里面进行配置,可以有in-memory、jdbc等多种读取方式。 jdbc需要调用JdbcClientDetailsService类,此类需要传入相应的DataSource. 下面再介绍一下如何管理token:AuthorizationServerTokenServices接口:声明必要的关于token的操作 (1)当token创建后,保存起来,以便之后的接受访问令牌的资源可以引用它。 (2)访问令牌用来加载认证 接口的实现也有多种,DefaultTokenServices是其默认实现,他使用了默认的InMemoryTokenStore,不会持久化token; token存储方式共有三种分别是:(1)InMemoryTokenStore:存放内存中,不会持久化 (2)JdbcTokenStore:存放数据库中 (3)Jwt: json web token 授权类型:可以通过AuthorizationServerEndpointsConfigurer来进行配置,默认情况下,支持除了密码外的所有授权类型。相关授权类型的一些类: (1)authenticationManager:直接注入一个AuthenticationManager,自动开启密码授权类型 (2)userDetailsService:如果注入UserDetailsService,那么将会启动刷新token授权类型,会判断用户是否还是存活的 (3)authorizationCodeServices:AuthorizationCodeServices的实例,auth code 授权类型的服务 (4)implicitGrantService:imlpicit grant (5)tokenGranter: endpoint的URL的配置:(1)AuthorizationServerEndpointsConfigurer的pathMapping()方法,有两个参数,第一个是默认的URL路径,第二个是自定义的路径 (2)WebSecurityConfigurer的实例,可以配置哪些路径不需要保护,哪些需要保护。默认全都保护。 自定义UI:(1)有时候,我们可能需要自定义的登录页面和认证页面。登陆页面的话,只需要创建一个login为前缀名的网页即可,在代码里,设置为允许访问,这样,系统会自动执行你的登陆页。此登陆页的action要注意一下,必须是跳转到认证的地址。 (2)另外一个是授权页,让你勾选选项的页面。此页面可以参考源码里的实现,自己生成一个controller的类,再创建一个对应的web页面即可实现自定义的功能。 下面梳理一下授权获取token流程:(1)端口号换成你自己的认证服务器的端口号,client_id也换成你自己的,response_type类型为code。 localhost:8080/uaa/oauth/authorize?client_id=client&response_type=code&redirect_uri=http://www.baidu.com(2)这时候你将获得一个code值:http://www.baidu.com/?code=G0C20Z (3)使用此code值来获取最终的token: curl -X POST -H "Cant-Type: application/x-www-form-urlencoded" -d 'grant_type=authorization_code&code=G0C20Z&redirect_uri=http://www.baidu.com' "http://client:secret@localhost:8080/uaa/oauth/token" 返回值: {"access_token":"b251b453-cc08-4520-9dd0-9aedf58e6ca3","token_type":"bearer","expires_in":2591324,"scope":"app"} (4)用此token值来调用资源服务器内容(如果资源服务器和认证服务器在同一个应用中,那么资源服务器会自己解析token值,如果不在,那么你要自己去做处理) curl -H "Authorization: Bearer b251b453-cc08-4520-9dd0-9aedf58e6ca3" "localhost:8081/service2(此处换上你自己的url)" 四、Resource Server:保护资源,需要令牌才能访问在配置类上加上注解@EnableResourceServer即启动。使用ResourceServerConfigurer进行配置: (1)tokenServices:ResourceServerTokenServices的实例,声明了token的服务 (2)resourceId:资源Id,由auth Server验证。 (3)其它一些扩展点,比如可以从请求中提取token的tokenExtractor (4)一些自定义的资源保护配置,通过HttpSecurity来设置 使用token的方式也有两种: (1)Bearer Token(https传输方式保证传输过程的安全):主流 (2)Mac(http+sign) 如何访问资源服务器中的API? 如果资源服务器和授权服务器在同一个应用程序中,并且您使用DefaultTokenServices,那么您不必太考虑这一点,因为它实现所有必要的接口,因此它是自动一致的。如果您的资源服务器是一个单独的应用程序,那么您必须确保您匹配授权服务器的功能,并提供知道如何正确解码令牌的ResourceServerTokenServices。与授权服务器一样,您可以经常使用DefaultTokenServices,并且选项大多通过TokenStore(后端存储或本地编码)表示。 (1)在校验request中的token时,使用RemoteTokenServices去调用AuthServer中的/auth/check_token。 (2)共享数据库,使用Jdbc存储和校验token,避免再去访问AuthServer。 (3)使用JWT签名的方式,资源服务器自己直接进行校验,不借助任何中间媒介。 五、oauth client在客户端获取到token之后,想去调用下游服务API时,为了能将token进行传递,可以使用RestTemplate.然后使用restTemplate进行调用Api。 注: scopes和authorities的区别: scopes是client权限,至少授予一个scope的权限,否则报错。 authorities是用户权限。 以上是我从网上找到的一篇写的不错的博客,希望可以帮助大家快速了解OAuth2.0,下一篇文章我们正式介绍OAuth2.0在当前框架中的使用。 从现在开始,我这边会将近期研发的spring cloud微服务云架构的搭建过程和精髓记录下来,帮助更多有兴趣研发spring cloud框架的朋友,大家来一起探讨spring cloud架构的搭建过程及如何运用于企业项目。完整项目的源码来源

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

放弃 SpringCloud Gateway!Apache APISIX 在「还呗」业务中的技术实践

不同行业之间,都会存在一些业务属性上的差距。对于金融领域的应用软件来说,因其涉及到钱等因素,所以在业务上会有以下独特属性: 稳定性。 金融领域跟钱强相关,这对于业务稳定性就有着非常严格的要求,稳定性一旦出现问题,它将影响整个交易系统的成败。 强监管。 强监管一般是针对生物医药领域、医疗领域和金融领域,因为它们所呈现的内容都与人的生命相关。所以,更高层面的强监管要求势必会影响一些业务层面的选型和架构呈现。 准确性和有效性。 由于跟钱强相关,所以在数字层面的呈现更是要求零偏差。就像股票价格一样,它的数字呈现都是精确到了每分每秒和固定数位的。 基于以上这些特点,金融行业软件系统在进行系统设计、机房拓扑以及中间件选型时,就会出现一些与其他通用行业不太一样的地方。 对 Java 的那些爱与恨 金融系统为何独爱 Java Java 自诞生以来就深受开发者的喜爱。在中国有将近 50% 的开发者在使用 Java 作为开发语言。这不单单是因为其语言的优势,也因为 Java 相关的生态非常庞大,尤其是国内的金融系统很多都是基于 Java 的,这导致有段时间大家都误以为所有的系统都是用 Java 做的。 近 15~20 年以来,大部分金融系统基本都选择了 Java 技术栈,深究其原因,我们认为主要是因为 Java 技术栈有以下几点优势。 正是如此,Java 逐渐得到了金融类软件系统的青睐。 云原生时代下的 Java 现状 随着技术行业的快速发展,单体架构逐渐被淘汰,微服务和云原生时代正在风靡四海。然而在近几年的技术大环境下,作为面向对象的高级语言,Java 也在一些业务场景中开始略显疲惫: 首先,Java 性能较低,这点对比一下 C 语言相关技术栈就会明白。Java 是基于虚拟机,它的内存管理是交给虚拟机来解决的,所以当面对一些高性能或动态变化的业务场景时,Java 语言在处理上没有那么强势。 其次,Java 语言需要更多的资源。一个架构的打造如果不考虑成本,很多问题都很好解决,但在云原生时代下,所有的资源计算变得越来越细、越来越颗粒化。Java 在运作时需要消耗大量的资源,由于 Java 分量重和需要重启的基础特性,因此在高 QPS 或者业务连续性要求较高的场景下,该语言会更容易出现问题。 最后就是指针变量的问题。习惯于写 C/C++ 语言的同学都知道,指针是一个非常好的资源。但 Java 是基于虚拟机,它把内存管理交给了 GC(Garbage Collection),而不是由手动程序进行管理,所以对于一些特定情况或者高并发、高访问量和高性能的场景下,Java 的实际性能可能就略显不足了。 还呗为何选择 APISIX? 数禾科技是一家提供智能化金融的服务平台,旗下主要产品有还呗、还享花等。还呗 APP 是一款基于消费多场景的分期服务平台,通过与持牌金融机构合作,为大众提供个人消费信贷服务,并为小微企业主提供贷款资金支持。在业务架构层面,还呗的产品实现一直是依赖 Java 技术栈的。 Spring Cloud Gateway 是 Spring Cloud 生态下为更好管理微服务而诞生的网关项目,对于公司业务以 Java 为主要开发语言的情况下,Spring Cloud Gateway 通常是个不错的 API 网关选择。但在近期的 API 网关迭代过程中,还呗放弃了使用已久的 Spring Cloud Gateway,而是选择了 Apache APISIX。 架构的前后变化 在架构层面,还呗在使用 APISIX 前后呈现了如下图所示的变化。 在左侧的使用前架构中,还呗一共使用了三套网关系统,并把网关分为入口网关和出口网关两大类。其中在运营系统网关和出口系统网关中,都使用了 Spring Cloud Gateway 作为网关,而在业务系统网关中则使用了 OpenRestry 作为业务系统网关。 对于一开始使用 Spring Cloud Gateway 作为运营和出口系统网关,主要是看中了 Spring Cloud 庞大的生态系统,以及简单易部署和易维护的分布式系统开发框架,所以在早期进行业务架构部署时,为了更快搭建起业务而选择使用 Spring Cloud 全家桶。 但随着业务慢慢发展,原先架构中的网关开始出现一些稳定性的问题,比如内存溢出、CPU 使用率过高等情况。为了升级网关性能及统一多个网关,还呗将架构中的网关全部统一替换为了 Apache APISIX。 在新网关架构中,业务系统网关会优先把请求流量通过服务发现的方式直接转发到业务系统。如果后端应用在 Consul 中没有健康 Pod 或者后端应用不支持服务发现等,就会把流量转发到以前的内网 K8s Ingress,作为兜底的上游来使用。 新架构同时也统一了出口网关的两个应用,新出口网关部署在 K8s 集群外的外联区。同时也在出口网关集群前新增一个 SLB,可以统一出口网关的入口 ,方便没有服务发现能力的应用或者其他 VPC 内的系统调用。 基于 APISIX 的应用实践 实际业务情况下,由于内部已存在多种网关架构,没办法直接使用 Apache APISIX,于是还呗基于 APISIX 进行了一些改造和构建。 APISIX 构建部署 在内部进行开发时,将 APISIX 网关的代码和定制代码存放在不同路径下,两者协同工作,各自可独立迭代。在部署时则采用 Docker 镜像方式部署,构建一个 APISIX 指定版本的基础镜像,然后再把自定义代码打包形成新镜像。 自定义代码打包时没有使用 lua_package_path 来指定代码目录,而是直接覆盖基础镜像 apisix 源码目录,如果有同名文件则覆盖源码文件。Dockerfile 如下所示: FROM registry.xxx.net:5001/apisix-shuhe:v1.5 ENV APP_NAME={{APP_NAME}} COPY {{PRODUCT_FILE}} /tmp/deploy2/artifact.tar.gz RUN mkdir /tmp/deploy/ && tar -xf /tmp/deploy2/artifact.tar.gz -C /tmp/deploy/ && \ cp -R /tmp/deploy/apisix/ /usr/local/apisix/ && \ cp /tmp/deploy/bin/apisix /usr/bin/apisix && \ cp /tmp/deploy/conf/apisix-$APP_NAME.yaml /usr/local/apisix/conf/apisix.yaml && \ cp /tmp/deploy/conf/config-$APP_NAME.yaml /usr/local/apisix/conf/config.yaml && \ set -x && \ bin='#! /usr/local/openresty/luajit/bin/luajit\npackage.path = "/usr/local/apisix/?.lua;" .. package.path' && \ sed -i "1s@.*@$bin@" /usr/bin/apisix && \ rm -rf /tmp/* APISIX 的日志默认存储在本地(也可以通过 Syslog 等插件收集),通过调整 nginx 配置模板和判断启用的 Profile 来决定日志存储在本地还是通过 Syslog 存储到 FLUENTD 中。同时在构建镜像时替换模板中 FLUENTD_HOST 变量。如下所示: {% if gw_profile and string.find( gw_profile,'local') then %} access_log logs/access.log main;error_log logs/ error.log warn; {%else%} access_log syslog:server=${FLUENTD_HOST}:5141 json_format; error_log syslog:server=${FLUENTD_HOST}:5142 warn; {%end%} 在 nginx 配置模板中,还呗不光修改了日志存储,还调整了循环添加 ENV 环境变量、循环添加 lua_shared_dicts 配置及写死一些 NGINX 其他调优参数。 因为公司是按照业务流量划分为多种网关,这些网关的基本功能都差不多,因此还呗内部采取了「一套代码部署多个网关应用」方案。通过 Profile 功能配置各个网关的 config-xxx.yaml 文件,然后通过公司 DEVOPS 平台构建镜像时,根据应用名构建不同网关的 Docker 镜像即可。 公司级定制插件 在内部访问运营系统页面时,会调用很多后端的 API 获取数据,这些 API 都需要在 API 网关中配置对应的白名单。在页面中根据登录运营系统用户的角色不同,能够访问的 API 范围也不一样,因此权限系统也需要维护相关 API 列表。每当在页面上新增后端 API 调用时 ,都需要开发人员在网关页面及权限系统页面配置两次,工作冗余且重复。 为此,还呗把网关配置与权限系统配置打通,只保留权限配置系统的配置入口,网关配置管理系统则定时拉取权限 API,之后转换成网关 API 白名单配置。这样做不仅能减少用户一次配置操作,同时也协助权限系统进行了权限管控。可以保证在运营页面调用的后端 API,一定是在权限系统配置了相关权限。 在公司的实际业务中,经常会遇到原生插件不能满足实际需求的情况,就需要定制开发。好在 APISIX 提供了很多工具类,参照原生插件就可以轻松实现,开发过程也非常简单。以下列举了还呗内部基于 APISIX 进行的其他定制插件: 网关流量灰度 之前还呗使用的 K8s 容器是 OpenShift(现已升级为 ACK 集群),其中 Ingress 是 Haproxy 搭建。由于公网 K8s Ingress 的 Haproxy 不能把一个域名的流量转发到两个 Namespace 的路由中,因此考虑把新网关部署在和老网关相同的 Namespace 下。即域名的路由下挂载多个服务,之后便可以通过路由调整流量比例,控制流量走新网关还是老网关。 具体实施流程如下图所示,在老网关的 Namespace 下新增 c、d 组用于部署新网关,通过路由控制新老网关的流量比例。 Java 层面网关的考虑因素 很多 Java 工程师在微服务架构中都会选择 Spring Cloud,主要是语言绑定,并用类库的方式放在代码里。但是在实践过程中可能会出现升级困难的情况,如果团队是多语言就需要维护多个类库,假设有 10 个版本与 10 种语言,就需要维护 100 个类库。 此时就可以通过代理的方式(即 API 网关)把多版本和多语言的问题轻松解决。那 Java 技术栈公司选择 APISIX 作为 API 网关后都有哪些收益?我们根据还呗的实践经历,从以下两个角度进行了总结。 公司角度 功能与性能兼具 还呗在内部使用 4 核虚拟机无插件空跑压测 APISIX 的 QPS 可以达到 80K,很好地解决了 Spring Cloud Gateway 在承接 C 端流量时出现的性能问题,而且在生产环境中发现 APISIX 相较于之前网关性能提升了 30% 以上。 其次,得益于云原生属性,APISIX 在实际的测试中完全可以满足公司的需求,比如认证鉴权、可观测性、服务发现、限流限速以及四层和七层流量转发。而在功能扩展方面,APISIX 也支持了 70 余款插件,大部分的业务可以使用其原生插件,很大程度上减少了开发工作。 业务支出成本下降 在使用 APISIX 之前,如果性能出现了瓶颈,公司只能通过不断的增加服务器来解决这个问题,因此相应的硬件成本也会非常的高。 还呗在进行成本计算时发现,使用 APISIX 后,服务器的数量大概减少了 60% 左右。统一技术栈后,业务上也可以很轻松地基于 APISIX 原生框架实现功能的扩展,节省了开发成本,加快了项目上线时间速度。 开发者角度 满足业务需求 业务中所使用的软件或技术都应该是为需求而服务。从实际测试结果及调研数据来看,APISIX 的稳定性、可观测性、可扩展性会更好。 软件最终服务于业务。如果业务需要,可以为公司节约资源,那么无论公司的技术栈是什么,都会使用最符合公司业务的组件。 降低维护成本 相比之前使用的 OpenResty,APISIX 的学习成本相对较低,维护起来也比较省心。同时,APISIX 丰富的插件简化了一些通用功能的实现与部署,大大节约了项目上线的时间。 同时利用 APISIX 强大的日志和动态调试功能,业务可以很轻松地排查出故障点,从而快速定位、节约时间。 总结:金融业务发展趋势 在过去的十年里,互联网金融从「野蛮生长」开始逐渐向「精耕细作」模式转变,这个转变主要涉及到的就是系统的变革。 在野蛮生长阶段,业务讲究的是效率。为了业务更快速地建设,在基础架构选择的时候,负责人更多是选择自己熟悉的语言架构进行搭建。不同的负责人便会选择使用不同的技术栈,因此留下了很多技术债务。从 2017 年开始,依旧活跃的金融企业或服务公司大多都会面临同样的技术现状,那就是存在多套技术组件。这时就需要进行基础设施的统一。 来到精耕细作阶段,企业就需要对系统进行垂直拆分,由以前的烟囱式拆分成前台、中台及后台等模式。系统到达一个稳定阶段时,就需要把一些东西夯实下来。 而系统建设的根本目的其实就是为了共用。重复性使用越强,系统的运维成本就越低。所以很多公司到了精耕细作阶段,要么是进行系统的垂直拆分,要么就是进行基础组件的下沉,进而控制运维成本。 作为企业来说,成本优先依旧是需要考虑的原则。野蛮生长阶段可能只需要尽快实现业务,而在目前大环境下,预算范围之内肯定是成本优先。这样的话,效率和成本永远只能保住一项。因此在成本有限的情况下,企业就会少谈技术的先进性。技术人员在选型的过程中,就不会考虑当下选择的这个技术对团队有多大冲击、对现有的运维和架构带来多少收益等等,更多是从成本角度考虑。

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

SpringCloud技术专题】「Hystrix」(9)熔断器的原理和实现机制

熔断器(Circuit Breaker)介绍 熔断器,现实生活中有一个很好的类比,就是家庭电路中都会安装一个保险盒,当电流过大的时候保险盒里面的保险丝会自动断掉,来保护家里的各种电器及电路。 Hystrix中的熔断器(Circuit Breaker)也是起到这样的作用,Hystrix在运行过程中会向每个commandKey对应的熔断器报告成功、失败、超时和拒绝的状态,熔断器维护计算统计的数据,根据这些统计的信息来确定熔断器是否打开。 如果打开,后续的请求都会被截断。然后会隔一段时间默认是5s,尝试半开,放入一部分流量请求进来,相当于对依赖服务进行一次健康检查,如果恢复,熔断器关闭,随后完全恢复调用。 如下图: 说明,上面说的commandKey,就是在初始化的时候设置的andCommandKey(HystrixCommandKey.Factory.asKey("testCommandKey")) 再来看下熔断器在整个Hystrix流程图中的位置,从步骤4开始,如下图: Hystrix会检查Circuit Breaker的状态。如果Circuit Breaker的状态为开启状态,Hystrix将不会执行对应指令,而是直接进入失败处理状态(图中8 Fallback)。 如果Circuit Breaker的状态为关闭状态,Hystrix会继续进行线程池、任务队列、信号量的检查(图中5) 如何使用熔断器(Circuit Breaker) 由于Hystrix是一个容错框架,因此我们在使用的时候,要达到熔断的目的只需配置一些参数就可以了。但我们要达到真正的效果,就必须要了解这些参数。Circuit Breaker一共包括如下6个参数。 circuitBreaker.enabled:是否启用熔断器,默认是TRUE。 circuitBreaker.forceOpen:熔断器强制打开,始终保持打开状态。默认值FLASE。 circuitBreaker.forceClosed:熔断器强制关闭,始终保持关闭状态。默认值FLASE。 circuitBreaker.errorThresholdPercentage:设定错误百分比,默认值50%,例如一段时间(10s)内有100个请求,其中有55个超时或者异常返回了,那么这段时间内的错误百分比是55%,大于了默认值50%,这种情况下触发熔断器-打开。 circuitBreaker.requestVolumeThreshold:默认值20。意思是至少有20个请求才进行errorThresholdPercentage错误百分比计算。比如一段时间(10s)内有19个请求全部失败了。错误百分比是100%,但熔断器不会打开,因为requestVolumeThreshold的值是20. 这个参数非常重要,熔断器是否打开首先要满足这个条件,源代码如下 // check if we are past the statisticalWindowVolumeThreshold if (health.getTotalRequests() < properties.circuitBreakerRequestVolumeThreshold().get()) { // we are not past the minimum volume threshold for the statisticalWindow so we'll return false immediately and not calculate anything return false; } if (health.getErrorPercentage() < properties.circuitBreakerErrorThresholdPercentage().get()) { return false; } circuitBreaker.sleepWindowInMilliseconds 半开试探休眠时间,默认值5000ms。当熔断器开启一段时间之后比如5000ms,会尝试放过去一部分流量进行试探,确定依赖服务是否恢复。 package myHystrix.threadpool; import com.netflix.hystrix.*; import org.junit.Test; import java.util.Random; /** * Created by wangxindong on 2017/8/15. */ public class GetOrderCircuitBreakerCommand extends HystrixCommand<String> { public GetOrderCircuitBreakerCommand(String name){ super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ThreadPoolTestGroup")) .andCommandKey(HystrixCommandKey.Factory.asKey("testCommandKey")) .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey(name)) .andCommandPropertiesDefaults( HystrixCommandProperties.Setter() .withCircuitBreakerEnabled(true)//默认是true,本例中为了展现该参数 .withCircuitBreakerForceOpen(false)//默认是false,本例中为了展现该参数 .withCircuitBreakerForceClosed(false)//默认是false,本例中为了展现该参数 .withCircuitBreakerErrorThresholdPercentage(5)//(1)错误百分比超过5% .withCircuitBreakerRequestVolumeThreshold(10)//(2)10s以内调用次数10次,同时满足(1)(2)熔断器打开 .withCircuitBreakerSleepWindowInMilliseconds(5000)//隔5s之后,熔断器会尝试半开(关闭),重新放进来请求 // .withExecutionTimeoutInMilliseconds(1000) ) .andThreadPoolPropertiesDefaults( HystrixThreadPoolProperties.Setter() .withMaxQueueSize(10) //配置队列大小 .withCoreSize(2) // 配置线程池里的线程数 ) ); } @Override protected String run() throws Exception { Random rand = new Random(); //模拟错误百分比(方式比较粗鲁但可以证明问题) if(1==rand.nextInt(2)){ // System.out.println("make exception"); throw new Exception("make exception"); } return "running: "; } @Override protected String getFallback() { // System.out.println("FAILBACK"); return "fallback: "; } public static class UnitTest{ @Test public void testCircuitBreaker() throws Exception{ for(int i=0;i<25;i++){ Thread.sleep(500); HystrixCommand<String> command = new GetOrderCircuitBreakerCommand("testCircuitBreaker"); String result = command.execute(); //本例子中从第11次,熔断器开始打开 System.out.println("call times:"+(i+1)+" result:"+result +" isCircuitBreakerOpen: "+command.isCircuitBreakerOpen()); //本例子中5s以后,熔断器尝试关闭,放开新的请求进来 } } } } 测试结果: call times:1 result:fallback: isCircuitBreakerOpen: false call times:2 result:running: isCircuitBreakerOpen: false call times:3 result:running: isCircuitBreakerOpen: false call times:4 result:fallback: isCircuitBreakerOpen: false call times:5 result:running: isCircuitBreakerOpen: false call times:6 result:fallback: isCircuitBreakerOpen: false call times:7 result:fallback: isCircuitBreakerOpen: false call times:8 result:fallback: isCircuitBreakerOpen: false call times:9 result:fallback: isCircuitBreakerOpen: false call times:10 result:fallback: isCircuitBreakerOpen: false 熔断器打开 call times:11 result:fallback: isCircuitBreakerOpen: true call times:12 result:fallback: isCircuitBreakerOpen: true call times:13 result:fallback: isCircuitBreakerOpen: true call times:14 result:fallback: isCircuitBreakerOpen: true call times:15 result:fallback: isCircuitBreakerOpen: true call times:16 result:fallback: isCircuitBreakerOpen: true call times:17 result:fallback: isCircuitBreakerOpen: true call times:18 result:fallback: isCircuitBreakerOpen: true call times:19 result:fallback: isCircuitBreakerOpen: true call times:20 result:fallback: isCircuitBreakerOpen: true 5s后熔断器关闭 call times:21 result:running: isCircuitBreakerOpen: false call times:22 result:running: isCircuitBreakerOpen: false call times:23 result:fallback: isCircuitBreakerOpen: false call times:24 result:running: isCircuitBreakerOpen: false call times:25 result:running: isCircuitBreakerOpen: false 熔断器(Circuit Breaker)源代码HystrixCircuitBreaker.java分析 Factory 是一个工厂类,提供HystrixCircuitBreaker实例 public static class Factory { //用一个ConcurrentHashMap来保存HystrixCircuitBreaker对象 private static ConcurrentHashMap<String, HystrixCircuitBreaker> circuitBreakersByCommand = new ConcurrentHashMap<String, HystrixCircuitBreaker>(); //Hystrix首先会检查ConcurrentHashMap中有没有对应的缓存的断路器,如果有的话直接返回。如果没有的话就会新创建一个HystrixCircuitBreaker实例,将其添加到缓存中并且返回 public static HystrixCircuitBreaker getInstance(HystrixCommandKey key, HystrixCommandGroupKey group, HystrixCommandProperties properties, HystrixCommandMetrics metrics) { HystrixCircuitBreaker previouslyCached = circuitBreakersByCommand.get(key.name()); if (previouslyCached != null) { return previouslyCached; } HystrixCircuitBreaker cbForCommand = circuitBreakersByCommand.putIfAbsent(key.name(), new HystrixCircuitBreakerImpl(key, group, properties, metrics)); if (cbForCommand == null) { return circuitBreakersByCommand.get(key.name()); } else { return cbForCommand; } } public static HystrixCircuitBreaker getInstance(HystrixCommandKey key) { return circuitBreakersByCommand.get(key.name()); } static void reset() { circuitBreakersByCommand.clear(); } } // HystrixCircuitBreakerImpl是HystrixCircuitBreaker的实现,allowRequest()、isOpen()、markSuccess()都会在HystrixCircuitBreakerImpl有默认的实现。 static class HystrixCircuitBreakerImpl implements HystrixCircuitBreaker { private final HystrixCommandProperties properties; private final HystrixCommandMetrics metrics; /* 变量circuitOpen来代表断路器的状态,默认是关闭 */ private AtomicBoolean circuitOpen = new AtomicBoolean(false); /* 变量circuitOpenedOrLastTestedTime记录着断路恢复计时器的初始时间,用于Open状态向Close状态的转换 */ private AtomicLong circuitOpenedOrLastTestedTime = new AtomicLong(); protected HystrixCircuitBreakerImpl(HystrixCommandKey key, HystrixCommandGroupKey commandGroup, HystrixCommandProperties properties, HystrixCommandMetrics metrics) { this.properties = properties; this.metrics = metrics; } /*用于关闭熔断器并重置统计数据*/ public void markSuccess() { if (circuitOpen.get()) { if (circuitOpen.compareAndSet(true, false)) { //win the thread race to reset metrics //Unsubscribe from the current stream to reset the health counts stream. This only affects the health counts view, //and all other metric consumers are unaffected by the reset metrics.resetStream(); } } } @Override public boolean allowRequest() { //是否设置强制开启 if (properties.circuitBreakerForceOpen().get()) { return false; } if (properties.circuitBreakerForceClosed().get()) {//是否设置强制关闭 isOpen(); // properties have asked us to ignore errors so we will ignore the results of isOpen and just allow all traffic through return true; } return !isOpen() || allowSingleTest(); } public boolean allowSingleTest() { long timeCircuitOpenedOrWasLastTested = circuitOpenedOrLastTestedTime.get(); //获取熔断恢复计时器记录的初始时间circuitOpenedOrLastTestedTime,然后判断以下两个条件是否同时满足: // 1) 熔断器的状态为开启状态(circuitOpen.get() == true) // 2) 当前时间与计时器初始时间之差大于计时器阈值circuitBreakerSleepWindowInMilliseconds(默认为 5 秒) //如果同时满足的话,表示可以从Open状态向Close状态转换。Hystrix会通过CAS操作将circuitOpenedOrLastTestedTime设为当前时间,并返回true。如果不同时满足,返回false,代表熔断器关闭或者计时器时间未到。 if (circuitOpen.get() && System.currentTimeMillis() > timeCircuitOpenedOrWasLastTested + properties.circuitBreakerSleepWindowInMilliseconds().get()) { // We push the 'circuitOpenedTime' ahead by 'sleepWindow' since we have allowed one request to try. // If it succeeds the circuit will be closed, otherwise another singleTest will be allowed at the end of the 'sleepWindow'. if (circuitOpenedOrLastTestedTime.compareAndSet(timeCircuitOpenedOrWasLastTested, System.currentTimeMillis())) { // if this returns true that means we set the time so we'll return true to allow the singleTest // if it returned false it means another thread raced us and allowed the singleTest before we did return true; } } return false; } @Override public boolean isOpen() { if (circuitOpen.get()) {//获取断路器的状态 // if we're open we immediately return true and don't bother attempting to 'close' ourself as that is left to allowSingleTest and a subsequent successful test to close return true; } // Metrics数据中获取HealthCounts对象 HealthCounts health = metrics.getHealthCounts(); // 检查对应的请求总数(totalCount)是否小于属性中的请求容量阈值circuitBreakerRequestVolumeThreshold,默认20,如果是的话表示熔断器可以保持关闭状态,返回false if (health.getTotalRequests() < properties.circuitBreakerRequestVolumeThreshold().get()) { return false; } //不满足请求总数条件,就再检查错误比率(errorPercentage)是否小于属性中的错误百分比阈值(circuitBreakerErrorThresholdPercentage,默认 50),如果是的话表示断路器可以保持关闭状态,返回 false if (health.getErrorPercentage() < properties.circuitBreakerErrorThresholdPercentage().get()) { return false; } else { // 如果超过阈值,Hystrix会判定服务的某些地方出现了问题,因此通过CAS操作将断路器设为开启状态,并记录此时的系统时间作为定时器初始时间,最后返回 true if (circuitOpen.compareAndSet(false, true)) { circuitOpenedOrLastTestedTime.set(System.currentTimeMillis()); return true; } else { return true; } } } } 总结 每个熔断器默认维护10个bucket,每秒一个bucket,每个blucket记录成功,失败,超时,拒绝的状态,默认错误超过50%且10秒内超过20个请求进行中断拦截。下图显示HystrixCommand或HystrixObservableCommand如何与HystrixCircuitBreaker及其逻辑和决策流程进行交互,包括计数器在断路器中的行为。

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

kunlun-admin v1.0.6 发布,基于 SpringCloud 的后台管理系统

昆仑管理系统 v1.0.6发布,更新日志: 1、更新升级web和service的Dockerfile文件; 2、升级SpringBoot至2.1.12; 3、优化pom配置; 4、更新升级Activiti初始化脚本; 5、优化并修复前端及后台若干问题; 系统介绍 昆仑管理系统是一套基于前后端分离架构的后台管理系统。kunlun-web 基于React + Umi(乌米) + Ant Design (蚂蚁金服) 构建开发,提供前端解决方案;kunlun-service 基于 SpringBoot 与 Spring Cloud 构建开发,提供后端基于微服务架构的解决方案。系统通过Apache Shiro与Jwt组件,用token进行数据交互认证,可快速开发并独立进行Docker容器化部署。 使用说明 1. npm安装前端依赖库,并启动kunlun-home-web与kunlun-system-web服务; 2. 启动PostgreSQL,执行kunlun-basedata-service与kunlun-system-service服务resources下的sql文件; 3. 启动RabbitMQ和Redis; 4. 启动注册中心kunlun-register-service; 5. 依次启动kunlun-gateway-service、kunlun-basedata-service与kunlun-system-service服务; 6. 访问URL:http://localhost:8000; 7. 输入账号:admin,密码:admin及验证码。 工具插件 页面截图

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

kunlun-admin v1.0.5 发布,基于 SpringCloud 的后台管理系统

昆仑管理系统 v1.0.5发布,更新日志: 1、调整首页快捷导航布局,使其更加紧凑; 2、修改用户管理、角色管理、在线用户等的查询功能; 3、调整首页资源模块显示名; 4、修改服务器资源使用情况统计功能; 5、优化并修复前端及后台若干问题; 系统介绍 昆仑管理系统是一套基于前后端分离架构的后台管理系统。kunlun-web 基于React + Umi(乌米) + Ant Design (蚂蚁金服) 构建开发,提供前端解决方案;kunlun-service 基于 SpringBoot 与 Spring Cloud 构建开发,提供后端基于微服务架构的解决方案。系统通过Apache Shiro与Jwt组件,用token进行数据交互认证,可快速开发并独立进行Docker容器化部署。 使用说明 1. npm安装前端依赖库,并启动kunlun-home-web与kunlun-system-web服务; 2. 启动PostgreSQL,执行kunlun-basedata-service与kunlun-system-service服务resources下的sql文件; 3. 启动RabbitMQ和Redis; 4. 启动注册中心kunlun-register-service; 5. 依次启动kunlun-gateway-service、kunlun-basedata-service与kunlun-system-service服务; 6. 访问URL:http://localhost:8000; 7. 输入账号:admin,密码:admin及验证码。 工具插件 页面截图

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

资深架构师解析springcloud分布式微服务的实现

分布式系统 微服务就是原来臃肿的项目拆分为多个模块互不关联。如:按照子服务拆分、数据库、接口,依次往下就更加细粒度,当然运维也就越来越难受了。 分布式则是偏向与机器将诺大的系统划分为多个模块部署在不同服务器上。 微服务和分布式就是作用的“目标不一样”。 微服务与Cloud 微服务是一种概念,spring-cloud是微服务的实现。 微服务也不一定必须使用cloud来实现,只是微服务中有许多问题,如:负载均衡、服务注册与发现、路由等等。 而cloud则是将这些处理问题的技术整合了。 Spring-Cloud 组件 Eureka Eureka是Netifix的子模块之一,Eureka有2个组件,一个EurekaServer 实现中间层服务器的负载均衡和故障转移,一个EurekaClient它使得与server交互变得简单。 Spring-Cloud封装了Netifix公司开发的Eureka模块来实现服务注册和发现。 通过Eureka的客户端 Eureka Server维持心跳连接,维护可以更方便监控各个微服务的运行。 角色关系图 Eureka使用 客户端 <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> server: port: 4001 eureka: client: serviceUrl: defaultZone: http://localhost:3000/eureka/ #eureka服务端提供的注册地址 参考服务端配置的这个路径 instance: instance-id: admin-1 #此实例注册到eureka服务端的唯一的实例ID prefer-ip-address: true #是否显示IP地址 leaseRenewalIntervalInSeconds: 10 #eureka客户需要多长时间发送心跳给eureka服务器,表明它仍然活着,默认为30 秒 (与下面配置的单位都是秒) leaseExpirationDurationInSeconds: 30 #Eureka服务器在接收到实例的最后一次发出的心跳后,需要等待多久才可以将此实例删除,默认为90秒 spring: application: name: server-admin #此实例注册到eureka服务端的name 服务端 <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter- netflix-eureka-server</artifactId> </dependency> yml文件声明 server: port: 3000 eureka: server: enable-self-preservation: false #关闭自我保护机制 eviction-interval-timer-in-ms: 4000 #设置清理间隔 (单位:毫秒 默认是60*1000) instance: hostname: localhost client: registerWithEureka: false #不把自己作为一个客户端注册到自己身上 fetchRegistry: false #不需要从服务端获取注册信息 (因为在这里自己就是服务端,而且已经禁用自己注册了) serviceUrl: defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka 在SpringBoot 启动项目中加入注解:@EnableEurekaServer 就可以启动项目了,访问对应地址就可以看到界面。 Eureka 集群 服务启动后Eureka Server会向其他服务server 同步,当消费者要调用服务提供者,则向服务注册中心获取服务提供者的地址,然后将提供者的地址缓存到本地,下次调用时候直接从本地缓存中获取 yml 服务端 server: port: 3000 eureka: server: enable-self-preservation: false #关闭自我保护机制 eviction-interval-timer-in-ms: 4000 #设置清理间隔 (单位:毫秒 默认是60*1000) instance: hostname: eureka3000.com client: registerWithEureka: false #不把自己作为一个客户端 注册到自己身上 fetchRegistry: false #不需要从服务端获取注册信息 (因为在这里自己就是服务端,而且已经禁用自己注册了) serviceUrl: defaultZone: http://eureka3001.com:3001/eureka, http://eureka3002.com:3002/eureka (这里不注册自己,注册到其他服务上面以为会同步。) yml 客户端 server: port: 4001 eureka: client: serviceUrl: defaultZone:http://localhost:3000/eureka/,http:// eureka3001.com:3001/eureka,http://eureka3002.com:3 002 /eureka #eureka服务端提供的注册地址 参考服务端配置的这个路径 instance: instance-id: admin-1 #此实例注册到eureka服务端的唯一的实例ID prefer-ip-address: true #是否显示IP地址 leaseRenewalIntervalInSeconds: 10 #eureka客户需要多长时间发 送心跳给eureka服务器,表明它仍然活着,默认为30 秒 (与下面配置的单位都是秒) leaseExpirationDurationInSeconds: 30 #Eureka服务器在 接收到实例的最后一次发出的心跳后,需要等待多久才可以将此实例删除,默认为90秒 spring: application: name: server-admin #此实例注册到eureka服务端的name CAP定理 C:Consistency 一致性 A:Availability 可用性 P:Partition tolerance 分区容错性 这三个指标不能同时达到 Partition tolerance 分区容错性,大多数分布式系统都部署在多个子网络。每一个网络是一个区。区间的通信是可能失败的如一个在本地,一个在外地,他们之间是无法通信的。分布式系统在设计的时候必须要考虑这种情况。 Consistency 一致性,写操作后的读取,必须返回该值。如:服务器A1和服务器A2,现在发起操作将A1中V0改为V1,用户去读取的时候读到服务器A1得到V1,如果读到A2服务器但是服务器 还是V0,读到的数据就不对,这就不满足一致性。 所以让A2返回的数据也对,的让A1给A2发送一条消息,把A2的V0变为V1,这时候不管从哪里读取都是修改后的数据。 Availability 可用性就是用户只要给出请求就必须回应,不管是本地服务器还是外地服务器只要接收到就必须做出回应,不管数据是否是最新必须做出回应,负责就不是可用性。 C与A矛盾 一致性和可用性不能同时成立,存在分区容错性,通信可能失败。 如果保证一致性,A1在写操作时,A2的读写只能被锁定,只有等数据同步了才能读写,在锁定期间是不能读写的就不符合可用性。 如果保持可用性,那么A2就不会被锁定,所以一致性就不能成立。 综上 无法做到一致性和可用性,所以系统在设计的时候就只能选其一。 Eureka与Zookeeper Zookeeper遵循的是CP原则保持了一致性,所以在master节点因为网络故障与剩余“跟随者”接点失去联系时会重新选举“领导者”,选取“领导者”大概会持续30-120s的时间,且选举的时候整个zookeeper是不可用的。导致在选举的时候注册服务瘫痪。 Eureka在设计的时候遵循AP可用性。Eureka各个接点是公平的,没有主从之分,down掉几个几点也没问题,其他接点依然可以支持注册,只要有一台Eureka在,注册就可以用,只不过查询到的数据可能不是最新的。Eureka有自我保护机制,如果15分钟之内超过85%接点都没有正常心跳,那么Eureka认为客户端与注册中心出现故障,此时情况可能是 Eureka不在从注册列表移除因为长时间没有瘦到心跳而过期的服务。 Eureka仍然能够接收注册和查询,但不会同步到其他接点。 当网络稳定后,当前的 实例注册信息会更新到其他接点。 Ribbon rebbon主要提供客户端的负载均衡,提供了一套完善的客户端的配置。Rebbin会自动帮助你基于某种规则(如:简单的轮询,随机链接等)。 服务端的负载均衡是一个url通过一个代理服务器,然后通过代理服务器(策略:轮询,随机 ,权重等等),反向代理到你的服务器。 客户端负载均衡是通过一个请求在客户端已经声明了要调用那个服务,然后通过具体算法来完成负载均衡。 Ribbon使用 引入依赖,Eureka以及把Ribbon集成在里面。 使用Ribbon只有在RestTemplate上面加入@LoadBalanced注解。 Feign负载均衡 feign是一个声明式的webService客户端,使用feign会让编写webService更简单,就是定义一个接口加上注解。 feign是为了编写java http客户端更加简单,在Ribbon+RestTemplate此基础上进一步封装,简化了使用Spring Cloud Ribbon时,自动封装服务调用客户端的开发量。 Feign使用 引入依赖 <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> 在启动类上加@EnableFeignClients 然后在接口上加@FeignClient("SERVER-POWER")注解其中参数就是服务的名字。 Feign集成Ribbon 利用Ribbon维护服务列表信息,融合了Ribbon的负载均衡配置,与Ribbon不同的是Feign只需要定义服务绑定接口以声明的方式,实现简答的服务调用。 hystrix断路器 是一种用于处理分布式系统延迟和容错的开源库。在分布式系统中许多依赖不可避免的会调用失败,比如超时、异常等,断路器保证出错不会导致整体服务失败,避免级联故障。 断路器其实就是一种开关设置,类似保险丝,像调用方返回一个符合预期的、可处理的备选响应,而不是长时间等待或者抛出无法处理的异常,保证服务调用方线程不会被长时间 不必要占用,从而避免了在分布式系统中蔓延,乃至雪崩。 微服务中 client->微服务A->微服务B->微服务C->微服务D,其中微服务B异常了,所有请求微服务A的请求都会卡在B这里,就会导致线程一直累积在这里,那么其他微服务就没有可用线程,导致整个服务器雪崩。 针对这方案有 服务限流、超时监控、服务熔断、服务降级 降级 超时 降级就是服务响应过长 ,或者不可用了,就是服务调用不了了,我们不能把错误信息返回出来,或者长时间卡在哪里,所以要准备一个策略当发生这种问题我们直接调用这个方法快速返回这个请求,不让他一直卡在那。 要在调用方做降级(要不然那个微服务都down掉了在做降级就没有意义)。 引入hystrix依赖 <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency> 在启动类上加入@EnableHystrix 或者@EnableCircuitBreaker。 @RequestMapping("/feignPower.do") @HystrixCommand(fallbackMethod = "fallbackMethod") public Object feignPower(String name){ return powerServiceClient.power(); } fallbackMethod: public Object fallbackMethod(String name){ System.out.println(name); return R.error("降级信息"); } 这里的降级信息具体内容根据业务需求来,比如返回一个默认的查询信息等等。 hystrix有超时监听,当你请求超过1秒 就会超时,这个是可以配置的 这里的降级信息具体内容根据业务需求来,比如返回一个默认的查询信息等等。 hystrix有超时监听,当你请求超过1秒 就会超时,这个是可以配置的 降级什么用 第一他可以监听服务有没有超时。第二报错了他这里直接截断了没有让请求一直卡在这个。 其实降级,当你系统迎来高并发的时候,这时候发现系统马上承载不了这个大的并发 ,可以先关闭一些不重要 的微服务(就是在降级方法返回一个比较友好的信息)把资源让出来给主服务,其实就是整体资源不够用了,忍痛关闭某些服务,待过渡后再打开。 熔断限流 熔断就像生活中的跳闸,比如电路故障了,为了防止事故扩大,这里切断你的电源以免意外发生。当一个微服务调用多次,hystrix就会采取熔断 机制,不在继续调用你的方法,会默认短路,5秒后试探性的先关闭熔断机制,如果在这时候失败一次会直接调用降级方法,一定程度避免雪崩, 限流,限制某个微服务使用量,如果线程占用超过了,超过的就会直接降级该次调用。 Feign整合hystrix feign默认支持hystrix,需要在yml配置中打开。 feign: hystrix: enabled: true 降级方法 @FeignClient(value = "SERVER-POWER", fallback = PowerServiceFallBack.class) public interface PowerServiceClient { @RequestMapping("/power.do") public Object power(@RequestParam("name") String name); } 在feign客户端的注解上 有个属性叫fallback 然后指向一个类 PowerServiceClient @Component public class PowerServiceFallBack implements PowerServiceClient { @Override public Object power(String name) { return R.error("测试降级"); } } Zuul 网关 zuul包含了对请求的路由和过滤两个主要功能 路由是将外部请求转发到具体的微服务实例上。是实现统一入口基础而过滤器功能负责对请求的处理过程干预,是实现请求校验等功能。 Zuul与Eureka进行整合,将zuul注册在Eureka服务治理下,同时从Eureka获取其他服务信息。(zuul分服务最终还是注册在Eureka上) 路由 <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-zuul</artifactId> </dependency> dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> 最后要注册在Eureka上所以需要引入eureka依赖 YML server: port: 9000 eureka: client: serviceUrl: defaultZone: http://localhost:3000/eureka/ #eureka服务端提供的注册地址 参考服务端配置的这个路径 instance: instance-id: zuul-0 #此实例注册到eureka服务端的唯一的实例ID prefer-ip-address: true #是否显示IP地址 leaseRenewalIntervalInSeconds: 10 #eureka客户需要多长时间发送心跳给eureka服务器,表明它仍然活着,默认为30 秒 (与下面配置的单位都是秒) leaseExpirationDurationInSeconds: 30 #Eureka服务器在接收到实例的最后一次发出的心跳后,需要等待多久才可以将此实例删除,默认为90秒 spring: application: name: zuul #此实例注册到eureka服务端的name 启动类 @EnableZuulProxy 在实际开发当中我们肯定不会/server-power这样通过微服务调用, 可能只要一个/power就好了 zuul: routes: mypower: serviceId: server-power path: /power/** myorder: serviceId: server-order path: /order/** 注意/**代表是所有层级 /* 是代表一层。 一般我们会禁用服务名调用 ignored-services:server-order 这样就不能通过此服务名调用, 不过这个配置如果一个一个通微服务名字设置太复杂 一般禁用服务名 ignored-services:“*” 有时候要考虑到接口调用需要一定的规范,比如调用微服务URL需要前缀/api,可以加上一个prefix prefix:/api 在加上strip-prefix: false /api前缀是不会出现在路由中 zuul: prefix: /api ignored-services: "*" stripPrefix: false routes: product: serviceId: server-product path: /product/** order: serviceId: server-order path: /order/** 过滤器 过滤器(filter)是zuul的核心组件,zuul大部分功能是通过过滤器实现的,zuul中定义了4种标准过滤器类型,这些过滤器类型对应与请求的生命周期, PRE:这种过滤器在请求路由前被调用,可利用过滤器进行身份验证,记录请求微服务的调试信息等。 ROUTING:这种过滤器将请求路由到微服务,这种过滤器用于构建发送给微服务请求,并使用 Apache HttpClient或Netfix Ribbon请求微服务。 POST:这种过滤器在路由微服务后执行,可用来相应添加标准的HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端。 ERROR:在其他阶段发送错误时执行过滤器 继承ZuulFilter @Component public class LogFilter extends ZuulFilter { @Override public String filterType() { return FilterConstants.PRE_TYPE; } @Override public int filterOrder() { return FilterConstants.PRE_DECORATION_FILTER_ORDER+1; } @Override public boolean shouldFilter() { return true; } @Override public Object run() throws ZuulException { RequestContext ctx = RequestContext.getCurrentContext(); //被代理到的微服务 String proxy = (String)ctx.get("proxy"); //请求的地址 String requestURI = (String)ctx.get("requestURI"); //zuul路由后的url System.out.println(proxy+"/"+requestURI); HttpServletRequest request = ctx.getRequest(); String loginCookie = CookieUtil.getLoginCookie(request); ctx.addZuulRequestHeader("login_key",loginCookie); return null; } } 由此可知道自定义zuul Filter要实现以下几个方法。 filterType:返回过滤器类型,有pre、route、post、erro等几种取值 filterOrder:返回一个int值指定过滤器的顺序,不同过滤器允许返回相同数字。 shouldFilter:返回一个boolean判断过滤器是否执行,true执行,false不执行。 run:过滤器的具体实现。 Spting-Cloud默认为zuul编写并开启一些过滤器。如果要禁用部分过滤器,只需在application.yml里设置zuul…disable=true,例如zuul.LogFilter.pre.disable=true zuul也整合了了hystrix和ribbon的, 提供降级回退,继承FallbackProvider 类 然后重写里面的方法。 私信我免费领取更多java架构资料、源码、笔记

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

kunlun-admin v1.0.2 发布,基于 SpringCloud 的后台管理系统

昆仑管理系统 v1.0.2发布,更新日志: 1、 升级图表组件Antv/g2plot至React专用的Ant-design/charts; 2、修改首页数据统计展示功能(用户统计、服务器CPU、集群资源、服务调用、Redis及MQ统计); 3、修改首页数据统计功能后台接口(服务器CPU、集群资源、服务调用、Redis及MQ统计); 4、修改退出后再次登录还停留在上次浏览页面的问题; 5、修改日志打印配置,只打印INFO级别; 6、修改v1.0.1中Swagger2无法访问的问题; 7、修改v1.0.1中使用定时任务处理缓存及数据库过期Token的问题; 7、用ES中zipkin的数据统计服务调用情况。 系统介绍 昆仑管理系统是一套基于前后端分离架构的后台管理系统。kunlun-web 基于React + Umi(乌米) + Ant Design (蚂蚁金服) 构建开发,提供前端解决方案;kunlun-service 基于 SpringBoot 与 Spring Cloud 构建开发,提供后端基于微服务架构的解决方案。系统通过Apache Shiro与Jwt组件,用token进行数据交互认证,可快速开发并独立进行Docker容器化部署。 使用说明 1. npm安装前端依赖库,并启动kunlun-home-web与kunlun-system-web服务; 2. 启动PostgreSQL,执行kunlun-basedata-service与kunlun-system-service服务resources下的sql文件; 3. 启动RabbitMQ和Redis; 4. 启动注册中心kunlun-register-service; 5. 依次启动kunlun-gateway-service、kunlun-basedata-service与kunlun-system-service服务; 6. 访问URL:http://localhost:8000; 7. 输入账号:admin,密码:admin及验证码。 工具插件 页面截图

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

kunlun-admin v1.0.0 发布,基于 SpringCloud 的后台管理系统

昆仑管理系统 v1.0.0 发布,基础功能: 1、提供公共基础模型、工具、自动配置、统一异常处理、统一Swagger配置及操作日志AOP等 2、服务注册、服务发现、服务心跳检测、高级消息队列(RabbitMQ)及分布式配置中心等; 3、服务路由、登录用户校验、鉴权及生成Token、Hystrix的turbine模式配置及Swagger路由配置等; 4、提供基础数据支持,如菜单、角色、权限等,并提供基于Redis的分布式缓存功能、基于ElasticSearch + RabbitMQ的服务调用追踪、资源爬取等; 5、 业务功能支持服务,提供业务数据、动态数据源、脚本自动执行及基于RabbitMQ的异步操作日志生成功能; 6、 基于docker容器化部署。 系统介绍 昆仑管理系统是一套基于前后端分离架构的后台管理系统。kunlun-web 基于React + Umi(乌米) + Ant Design (蚂蚁金服) 构建开发,提供前端解决方案;kunlun-service 基于 SpringBoot 与 Spring Cloud 构建开发,提供后端基于微服务架构的解决方案。系统通过Apache Shiro与Jwt组件,用token进行数据交互认证,可快速开发并独立进行Docker容器化部署。 使用说明 1. npm安装前端依赖库,并启动kunlun-home-web与kunlun-system-web服务; 2. 启动PostgreSQL,执行kunlun-basedata-service与kunlun-system-service服务resources下的sql文件; 3. 启动RabbitMQ和Redis; 4. 启动注册中心kunlun-register-service; 5. 依次启动kunlun-gateway-service、kunlun-basedata-service与kunlun-system-service服务; 6. 访问URL:http://localhost:8000; 7. 输入账号:admin,密码:admin及验证码。 工具插件 页面截图

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

SpringCloud实现分库分表模式下,数据库实时扩容方案

一、项目结构 1、工程结构 2、模块命名 shard-common-entity: 公共代码块shard-open-inte: 开放接口管理shard-eureka-7001: 注册中心shard-two-provider-8001: 8001 基于两台库的服务shard-three-provider-8002:8002 基于三台库的服务 3、代码依赖结构 4、项目启动顺序 (1)shard-eureka-7001: 注册中心(2)shard-two-provider-8001: 8001 基于两台库的服务(3)shard-three-provider-8002:8002 基于三台库的服务 按照顺序启动,且等一个服务完全启动后,在启动下一个服务,不然可能遇到一些坑。 二、核心代码块 1、8001 服务提供一个对外服务 基于Feign的调用方式 作用:基于两台分库分表的数据查询接口。 import org.springframework.cloud.netflix.feign.FeignClient;import org.springframework.web.bind.annotation.PathVariable;import org.springframework.web.bind.annotation.RequestMapping;import shard.jdbc.common.entity.TableOne;/** * shard-two-provider-8001 * 对外开放接口 */@FeignClient(value = "shard-provider-8001")public interface TwoOpenService { @RequestMapping("/selectOneByPhone/{phone}") TableOne selectOneByPhone(@PathVariable("phone") String phone) ;} 2、8002 服务提供一个对外服务 基于Feign的调用方式 作用:基于三台分库分表的数据存储接口。 import org.springframework.cloud.netflix.feign.FeignClient;import org.springframework.web.bind.annotation.RequestBody;import org.springframework.web.bind.annotation.RequestMapping;import shard.jdbc.common.entity.TableOne;/** * 数据迁移服务接口 */@FeignClient(value = "shard-provider-8002")public interface MoveDataService { @RequestMapping("/moveData") Integer moveData (@RequestBody TableOne tableOne) ;} 3、基于8002服务数据查询接口 查询流程图 代码块 /** * 8001 端口 :基于两台分库分表策略的数据查询接口 */@Resourceprivate TwoOpenService twoOpenService ;@Overridepublic TableOne selectOneByPhone(String phone) { TableOne tableOne = tableOneMapper.selectOneByPhone(phone); if (tableOne != null){ LOG.info("8002 === >> tableOne :"+tableOne); } // 8002 服务没有查到数据 if (tableOne == null){ // 调用 8001 开放的查询接口 tableOne = twoOpenService.selectOneByPhone(phone) ; LOG.info("8001 === >> tableOne :"+tableOne); } return tableOne ;} 4、基于 8001 数据扫描迁移代码 迁移流程图 代码块 /** * 8002 端口开放的数据入库接口 */@Resourceprivate MoveDataService moveDataService ;/** * 扫描,并迁移数据 * 以 库 db_2 的 table_one_1 表为例 */@Overridepublic void scanDataRun() { String sql = "SELECT id,phone,back_one backOne,back_two backTwo,back_three backThree FROM table_one_1" ; // dataTwoTemplate 对应的数据库:ds_2 List<TableOne> tableOneList = dataTwoTemplate.query(sql,new Object[]{},new BeanPropertyRowMapper<>(TableOne.class)) ; if (tableOneList != null && tableOneList.size()>0){ int i = 0 ; for (TableOne tableOne : tableOneList) { String db_num = HashUtil.moveDb(tableOne.getPhone()) ; String tb_num = HashUtil.moveTable(tableOne.getPhone()) ; // 只演示向数据新加库 ds_4 迁移的数据 if (db_num.equals("ds_4")){ i += 1 ; LOG.info("迁移总数数=>" + i + "=>库位置=>"+db_num+"=>表位置=>"+tb_num+"=>数据:【"+tableOne+"】"); // 扫描完成:执行新库迁移和旧库清理过程 moveDataService.moveData(tableOne) ; // dataTwoTemplate.update("DELETE FROM table_one_1 WHERE id=? AND phone=?",tableOne.getId(),tableOne.getPhone()); } } }} 三、演示执行流程 1、项目流程图 2、测试执行流程 (1)、访问8002 数据查询端口 http://127.0.0.1:8002/selectOneByPhone/phone20日志输出:8001 服务查询到数据8001 === >> tableOne :+{tableOne} (2)、执行8001 数据扫描迁移 http://127.0.0.1:8001/scanData (3)、再次访问8002 数据查询端口 http://127.0.0.1:8002/selectOneByPhone/phone20日志输出:8002 服务查询到数据8002 === >> tableOne :+{tableOne} 四、源代码地址 GitHub·地址: https://github.com/cicadasmile/spring-cloud-base GitEE·地址: https://gitee.com/cicadasmile/spring-cloud-base

资源下载

更多资源
优质分享App

优质分享App

近一个月的开发和优化,本站点的第一个app全新上线。该app采用极致压缩,本体才4.36MB。系统里面做了大量数据访问、缓存优化。方便用户在手机上查看文章。后续会推出HarmonyOS的适配版本。

Nacos

Nacos

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

Rocky Linux

Rocky Linux

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

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

用户登录
用户注册