Gateway服务网关
大苹果

Gateway服务网关

3.Gateway服务网关SpringCloudGateway是SpringCloud的一个全新项目,该项目是基于Spring5.0,SpringBoot2.0和ProjectReactor等响应式编程和事件流技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的API路由管理方式。3.1.为什么需要网关Gateway网关是我们服务的守门神,所有微服务的统一入口。网关的核心功能特性:请求路由权限控制限流架构图:权限控制:网关作为微服务入口,需要校验用户是是否有请求资格,如果没有则进行拦截。路由和负载均衡:一切请求都必须先经过gateway,但网关不处理业务,而是根据某种规则,把请求转发到某个微服务,这个过程叫做路由。当然路由的目标服务有多个时,还需要做负载均衡。限流:当请求流量过高时,在网关中按照下流的微服务能够接受的速度来放行请求,避免服务压力过大。在SpringCloud中网关的实现包括两种:gatewayzuulZuul是基于Servlet的实现,属于阻塞式编程。而SpringCloudGateway则是基于Spring5中提供的WebFlux,属于响应式编程的实现,具备更好的性能。3.2.gateway快速入门下面,我们就演示下网关的基本路由功能。基本步骤如下:创建SpringBoot工程gateway,引入网关依赖编写启动类编写基础配置和路由规则启动网关服务进行测试1)创建gateway服务,引入依赖创建服务:引入依赖:<!--网关--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId></dependency><!--nacos服务发现依赖--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency>2)编写启动类packagecn.itcast.gateway;importorg.springframework.boot.SpringApplication;importorg.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplicationpublicclassGatewayApplication{publicstaticvoidmain(String[]args){SpringApplication.run(GatewayApplication.class,args);}}3)编写基础配置和路由规则创建application.yml文件,内容如下:server:port:10010#网关端口spring:application:name:gateway#服务名称cloud:nacos:server-addr:localhost:8848#nacos地址gateway:routes:#网关路由配置-id:user-service#路由id,自定义,只要唯一即可#uri:http://127.0.0.1:8081#路由的目标地址http就是固定地址uri:lb://userservice#路由的目标地址lb就是负载均衡,后面跟服务名称predicates:#路由断言,也就是判断请求是否符合路由规则的条件-Path=/user/**#这个是按照路径匹配,只要以/user/开头就符合要求我们将符合Path规则的一切请求,都代理到uri参数指定的地址。本例中,我们将/user/**开头的请求,代理到lb://userservice,lb是负载均衡,根据服务名拉取服务列表,实现负载均衡。4)重启测试重启网关,访问http://localhost:10010/user/1时,符合/user/**规则,请求转发到uri:http://userservice/user/1,得到了结果:5)网关路由的流程图整个访问的流程如下:总结:网关搭建步骤:创建项目,引入nacos服务发现和gateway依赖配置application.yml,包括服务基本信息、nacos地址、路由路由配置包括:路由id:路由的唯一标示路由目标(uri):路由的目标地址,http代表固定地址,lb代表根据服务名负载均衡路由断言(predicates):判断路由的规则,路由过滤器(filters):对请求或响应做处理接下来,就重点来学习路由断言和路由过滤器的详细知识3.3.断言工厂我们在配置文件中写的断言规则只是字符串,这些字符串会被PredicateFactory读取并处理,转变为路由判断的条件例如Path=/user/**是按照路径匹配,这个规则是由org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory类来处理的,像这样的断言工厂在SpringCloudGateway还有十几个:名称说明示例After是某个时间点后的请求-After=2037-01-20T17:42:47.789-07:00[America/Denver]Before是某个时间点之前的请求-Before=2031-04-13T15:14:47.433+08:00[Asia/Shanghai]Between是某两个时间点之前的请求-Between=2037-01-20T17:42:47.789-07:00[America/Denver],2037-01-21T17:42:47.789-07:00[America/Denver]Cookie请求必须包含某些cookie-Cookie=chocolate,ch.pHeader请求必须包含某些header-Header=X-Request-Id,\d+Host请求必须是访问某个host(域名)-Host=.somehost.org,.anotherhost.orgMethod请求方式必须是指定方式-Method=GET,POSTPath请求路径必须符合指定规则-Path=/red/{segment},/blue/**Query请求参数必须包含指定参数-Query=name,Jack或者-Query=nameRemoteAddr请求者的ip必须是指定范围-RemoteAddr=192.168.1.1/24Weight权重处理我们只需要掌握Path这种路由工程就可以了。3.4.过滤器工厂GatewayFilter是网关中提供的一种过滤器,可以对进入网关的请求和微服务返回的响应做处理:3.4.1.路由过滤器的种类Spring提供了31种不同的路由过滤器工厂。例如:名称说明AddRequestHeader给当前请求添加一个请求头RemoveRequestHeader移除请求中的一个请求头AddResponseHeader给响应结果中添加一个响应头RemoveResponseHeader从响应结果中移除有一个响应头RequestRateLimiter限制请求的流量3.4.2.请求头过滤器下面我们以AddRequestHeader为例来讲解。需求:给所有进入userservice的请求添加一个请求头:Truth=itcastisfreakingawesome!只需要修改gateway服务的application.yml文件,添加路由过滤即可:spring:cloud:gateway:routes:-id:user-serviceuri:lb://userservicepredicates:-Path=/user/**filters:#过滤器-AddRequestHeader=Truth,Itcastisfreakingawesome!#添加请求头当前过滤器写在userservice路由下,因此仅仅对访问userservice的请求有效。3.4.3.默认过滤器如果要对所有的路由都生效,则可以将过滤器工厂写到default下。格式如下:spring:cloud:gateway:routes:-id:user-serviceuri:lb://userservicepredicates:-Path=/user/**default-filters:#默认过滤项-AddRequestHeader=Truth,Itcastisfreakingawesome!3.4.4.总结过滤器的作用是什么?①对路由的请求或响应做加工处理,比如添加请求头②配置在路由下的过滤器只对当前路由的请求生效defaultFilters的作用是什么?①对所有路由都生效的过滤器3.5.全局过滤器上一节学习的过滤器,网关提供了31种,但每一种过滤器的作用都是固定的。如果我们希望拦截请求,做自己的业务逻辑则没办法实现。3.5.1.全局过滤器作用全局过滤器的作用也是处理一切进入网关的请求和微服务响应,与GatewayFilter的作用一样。区别在于GatewayFilter通过配置定义,处理逻辑是固定的;而GlobalFilter的逻辑需要自己写代码实现。定义方式是实现GlobalFilter接口。publicinterfaceGlobalFilter{/***处理当前请求,有必要的话通过{@linkGatewayFilterChain}将请求交给下一个过滤器处理**@paramexchange请求上下文,里面可以获取Request、Response等信息*@paramchain用来把请求委托给下一个过滤器*@return{@codeMono<Void>}返回标示当前过滤器业务结束*/Mono<Void>filter(ServerWebExchangeexchange,GatewayFilterChainchain);}在filter中编写自定义逻辑,可以实现下列功能:登录状态判断权限校验请求限流等3.5.2.自定义全局过滤器需求:定义全局过滤器,拦截请求,判断请求的参数是否满足下面条件:参数中是否有authorization,authorization参数值是否为admin如果同时满足则放行,否则拦截实现:在gateway中定义一个过滤器:packagecn.itcast.gateway.filters;importorg.springframework.cloud.gateway.filter.GatewayFilterChain;importorg.springframework.cloud.gateway.filter.GlobalFilter;importorg.springframework.core.annotation.Order;importorg.springframework.http.HttpStatus;importorg.springframework.stereotype.Component;importorg.springframework.web.server.ServerWebExchange;importreactor.core.publisher.Mono;@Order(-1)@ComponentpublicclassAuthorizeFilterimplementsGlobalFilter{@OverridepublicMono<Void>filter(ServerWebExchangeexchange,GatewayFilterChainchain){//1.获取请求参数MultiValueMap<String,String>params=exchange.getRequest().getQueryParams();//2.获取authorization参数Stringauth=params.getFirst("authorization");//3.校验if("admin".equals(auth)){//放行returnchain.filter(exchange);}//4.拦截//4.1.禁止访问,设置状态码exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);//4.2.结束处理returnexchange.getResponse().setComplete();}}3.5.3.过滤器执行顺序请求进入网关会碰到三类过滤器:当前路由的过滤器、DefaultFilter、GlobalFilter请求路由后,会将当前路由过滤器和DefaultFilter、GlobalFilter,合并到一个过滤器链(集合)中,排序后依次执行每个过滤器:排序的规则是什么呢?每一个过滤器都必须指定一个int类型的order值,order值越小,优先级越高,执行顺序越靠前。GlobalFilter通过实现Ordered接口,或者添加@Order注解来指定order值,由我们自己指定路由过滤器和defaultFilter的order由Spring指定,默认是按照声明顺序从1递增。当过滤器的order值一样时,会按照defaultFilter>路由过滤器>GlobalFilter的顺序执行。详细内容,可以查看源码:org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator#getFilters()方法是先加载defaultFilters,然后再加载某个route的filters,然后合并。org.springframework.cloud.gateway.handler.FilteringWebHandler#handle()方法会加载全局过滤器,与前面的过滤器合并后根据order排序,组织过滤器链3.6.跨域问题3.6.1.什么是跨域问题跨域:域名不一致就是跨域,主要包括:域名不同:www.taobao.com和www.taobao.org和www.jd.com和miaosha.jd.com域名相同,端口不同:localhost:8080和localhost8081跨域问题:浏览器禁止请求的发起者与服务端发生跨域ajax请求,请求被浏览器拦截的问题解决方案:CORS,这个以前应该学习过,这里不再赘述了。不知道的小伙伴可以查看https://www.ruanyifeng.com/blog/2016/04/cors.html3.6.2.模拟跨域问题找到课前资料的页面文件:放入tomcat或者nginx这样的web服务器中,启动并访问。可以在浏览器控制台看到下面的错误:从localhost:8090访问localhost:10010,端口不同,显然是跨域的请求。3.6.3.解决跨域问题在gateway服务的application.yml文件中,添加下面的配置:spring:cloud:gateway:#。。。globalcors:#全局的跨域处理add-to-simple-url-handler-mapping:true#解决options请求被拦截问题corsConfigurations:'[/**]':allowedOrigins:#允许哪些网站的跨域请求-"http://localhost:8090"allowedMethods:#允许的跨域ajax的请求方式-"GET"-"POST"-"DELETE"-"PUT"-"OPTIONS"allowedHeaders:"*"#允许在请求中携带的头信息allowCredentials:true#是否允许携带cookiemaxAge:360000#这次跨域检测的有效期

Spring Cloud微服务 17 30天前
Feign远程调用
大苹果

Feign远程调用

2.Feign远程调用先来看我们以前利用RestTemplate发起远程调用的代码:存在下面的问题:•代码可读性差,编程体验不统一•参数复杂URL难以维护Feign是一个声明式的http客户端,官方地址:https://github.com/OpenFeign/feign其作用就是帮助我们优雅的实现http请求的发送,解决上面提到的问题。2.1.Feign替代RestTemplateFegin的使用步骤如下:1)引入依赖我们在order-service服务的pom文件中引入feign的依赖:<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency>2)添加注解在order-service的启动类添加注解开启Feign的功能:3)编写Feign的客户端在order-service中新建一个接口,内容如下:packagecn.itcast.order.client;importcn.itcast.order.pojo.User;importorg.springframework.cloud.openfeign.FeignClient;importorg.springframework.web.bind.annotation.GetMapping;importorg.springframework.web.bind.annotation.PathVariable;@FeignClient("userservice")publicinterfaceUserClient{@GetMapping("/user/{id}")UserfindById(@PathVariable("id")Longid);}这个客户端主要是基于SpringMVC的注解来声明远程调用的信息,比如:服务名称:userservice请求方式:GET请求路径:/user/{id}请求参数:Longid返回值类型:User这样,Feign就可以帮助我们发送http请求,无需自己使用RestTemplate来发送了。4)测试修改order-service中的OrderService类中的queryOrderById方法,使用Feign客户端代替RestTemplate:是不是看起来优雅多了。5)总结使用Feign的步骤:①引入依赖②添加@EnableFeignClients注解③编写FeignClient接口④使用FeignClient中定义的方法代替RestTemplate2.2.自定义配置Feign可以支持很多的自定义配置,如下表所示:类型作用说明feign.Logger.Level修改日志级别包含四种不同的级别:NONE、BASIC、HEADERS、FULLfeign.codec.Decoder响应结果的解析器http远程调用的结果做解析,例如解析json字符串为java对象feign.codec.Encoder请求参数编码将请求参数编码,便于通过http请求发送feign.Contract支持的注解格式默认是SpringMVC的注解feign.Retryer失败重试机制请求失败的重试机制,默认是没有,不过会使用Ribbon的重试一般情况下,默认值就能满足我们使用,如果要自定义时,只需要创建自定义的@Bean覆盖默认Bean即可。下面以日志为例来演示如何自定义配置。2.2.1.配置文件方式基于配置文件修改feign的日志级别可以针对单个服务:feign:client:config:userservice:#针对某个微服务的配置loggerLevel:FULL#日志级别也可以针对所有服务:feign:client:config:default:#这里用default就是全局配置,如果是写服务名称,则是针对某个微服务的配置loggerLevel:FULL#日志级别而日志的级别分为四种:NONE:不记录任何日志信息,这是默认值。BASIC:仅记录请求的方法,URL以及响应状态码和执行时间HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据。2.2.2.Java代码方式也可以基于Java代码来修改日志级别,先声明一个类,然后声明一个Logger.Level的对象:publicclassDefaultFeignConfiguration{@BeanpublicLogger.LevelfeignLogLevel(){returnLogger.Level.BASIC;//日志级别为BASIC}}如果要全局生效,将其放到启动类的@EnableFeignClients这个注解中:@EnableFeignClients(defaultConfiguration=DefaultFeignConfiguration.class)如果是局部生效,则把它放到对应的@FeignClient这个注解中:@FeignClient(value="userservice",configuration=DefaultFeignConfiguration.class)2.3.Feign使用优化Feign底层发起http请求,依赖于其它的框架。其底层客户端实现包括:•URLConnection:默认实现,不支持连接池•ApacheHttpClient:支持连接池•OKHttp:支持连接池因此提高Feign的性能主要手段就是使用连接池代替默认的URLConnection。这里我们用Apache的HttpClient来演示。1)引入依赖在order-service的pom文件中引入Apache的HttpClient依赖:<!--httpClient的依赖--><dependency><groupId>io.github.openfeign</groupId><artifactId>feign-httpclient</artifactId></dependency>2)配置连接池在order-service的application.yml中添加配置:feign:client:config:default:#default全局的配置loggerLevel:BASIC#日志级别,BASIC就是基本的请求和响应信息httpclient:enabled:true#开启feign对HttpClient的支持max-connections:200#最大的连接数max-connections-per-route:50#每个路径的最大连接数接下来,在FeignClientFactoryBean中的loadBalance方法中打断点:Debug方式启动order-service服务,可以看到这里的client,底层就是ApacheHttpClient:总结,Feign的优化:1.日志级别尽量用basic2.使用HttpClient或OKHttp代替URLConnection①引入feign-httpClient依赖②配置文件开启httpClient功能,设置连接池参数2.4.最佳实践所谓最近实践,就是使用过程中总结的经验,最好的一种使用方式。自习观察可以发现,Feign的客户端与服务提供者的controller代码非常相似:feign客户端:UserController:有没有一种办法简化这种重复的代码编写呢?2.4.1.继承方式一样的代码可以通过继承来共享:1)定义一个API接口,利用定义方法,并基于SpringMVC注解做声明。2)Feign客户端和Controller都集成改接口优点:简单实现了代码共享缺点:服务提供方、服务消费方紧耦合参数列表中的注解映射并不会继承,因此Controller中必须再次声明方法、参数列表、注解2.4.2.抽取方式将Feign的Client抽取为独立模块,并且把接口有关的POJO、默认的Feign配置都放到这个模块中,提供给所有消费者使用。例如,将UserClient、User、Feign的默认配置都抽取到一个feign-api包中,所有微服务引用该依赖包,即可直接使用。2.4.3.实现基于抽取的最佳实践1)抽取首先创建一个module,命名为feign-api:项目结构:在feign-api中然后引入feign的starter依赖<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency>然后,order-service中编写的UserClient、User、DefaultFeignConfiguration都复制到feign-api项目中2)在order-service中使用feign-api首先,删除order-service中的UserClient、User、DefaultFeignConfiguration等类或接口。在order-service的pom文件中中引入feign-api的依赖:<dependency><groupId>cn.itcast.demo</groupId><artifactId>feign-api</artifactId><version>1.0</version></dependency>修改order-service中的所有与上述三个组件有关的导包部分,改成导入feign-api中的包3)重启测试重启后,发现服务报错了:这是因为UserClient现在在cn.itcast.feign.clients包下,而order-service的@EnableFeignClients注解是在cn.itcast.order包下,不在同一个包,无法扫描到UserClient。4)解决扫描包问题方式一:指定Feign应该扫描的包:@EnableFeignClients(basePackages="cn.itcast.feign.clients")方式二:指定需要加载的Client接口:@EnableFeignClients(clients={UserClient.class})

Spring Cloud微服务 25 30天前
Nacos配置管理
大苹果

Nacos配置管理

1.Nacos配置管理Nacos除了可以做注册中心,同样可以做配置管理来使用。1.1.统一配置管理当微服务部署的实例越来越多,达到数十、数百时,逐个修改微服务配置就会让人抓狂,而且很容易出错。我们需要一种统一配置管理方案,可以集中管理所有实例的配置。Nacos一方面可以将配置集中管理,另一方可以在配置变更时,及时通知微服务,实现配置的热更新。1.1.1.在nacos中添加配置文件如何在nacos中管理配置呢?然后在弹出的表单中,填写配置信息:注意:项目的核心配置,需要热更新的配置才有放到nacos管理的必要。基本不会变更的一些配置还是保存在微服务本地比较好。1.1.2.从微服务拉取配置微服务要拉取nacos中管理的配置,并且与本地的application.yml配置合并,才能完成项目启动。但如果尚未读取application.yml,又如何得知nacos地址呢?因此spring引入了一种新的配置文件:bootstrap.yaml文件,会在application.yml之前被读取,流程如下:1)引入nacos-config依赖首先,在user-service服务中,引入nacos-config的客户端依赖:<!--nacos配置管理依赖--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId></dependency>2)添加bootstrap.yaml然后,在user-service中添加一个bootstrap.yaml文件,内容如下:spring:application:name:userservice#服务名称profiles:active:dev#开发环境,这里是devcloud:nacos:server-addr:localhost:8848#Nacos地址config:file-extension:yaml#文件后缀名如果有鉴权,配置如下spring:application:name:userservice#服务名称profiles:active:dev#开发环境,这里是devcloud:nacos:server-addr:localhost:8848#Nacos地址username:nacospassword:nacosconfig:username:nacospassword:nacosnamespace:""#如果为默认命名空间则填空,否则会一直获取配置,非默认则填写命名空间名字file-extension:yaml#文件后缀名这里会根据spring.cloud.nacos.server-addr获取nacos地址,再根据${spring.application.name}-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}作为文件id,来读取配置。本例中,就是去读取userservice-dev.yaml:3)读取nacos配置在user-service中的UserController中添加业务逻辑,读取pattern.dateformat配置:完整代码:packagecn.itcast.user.web;importcn.itcast.user.pojo.User;importcn.itcast.user.service.UserService;importlombok.extern.slf4j.Slf4j;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.beans.factory.annotation.Value;importorg.springframework.web.bind.annotation.*;importjava.time.LocalDateTime;importjava.time.format.DateTimeFormatter;@Slf4j@RestController@RequestMapping("/user")publicclassUserController{@AutowiredprivateUserServiceuserService;@Value("${pattern.dateformat}")privateStringdateformat;@GetMapping("now")publicStringnow(){returnLocalDateTime.now().format(DateTimeFormatter.ofPattern(dateformat));}//...略}在页面访问,可以看到效果:1.2.配置热更新我们最终的目的,是修改nacos中的配置后,微服务中无需重启即可让配置生效,也就是配置热更新。要实现配置热更新,可以使用两种方式:1.2.1.方式一在@Value注入的变量所在类上添加注解@RefreshScope:1.2.2.方式二使用@ConfigurationProperties注解代替@Value注解。在user-service服务中,添加一个类,读取patterrn.dateformat属性:packagecn.itcast.user.config;importlombok.Data;importorg.springframework.boot.context.properties.ConfigurationProperties;importorg.springframework.stereotype.Component;@Component@Data@ConfigurationProperties(prefix="pattern")publicclassPatternProperties{privateStringdateformat;}在UserController中使用这个类代替@Value:完整代码:packagecn.itcast.user.web;importcn.itcast.user.config.PatternProperties;importcn.itcast.user.pojo.User;importcn.itcast.user.service.UserService;importlombok.extern.slf4j.Slf4j;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.web.bind.annotation.GetMapping;importorg.springframework.web.bind.annotation.PathVariable;importorg.springframework.web.bind.annotation.RequestMapping;importorg.springframework.web.bind.annotation.RestController;importjava.time.LocalDateTime;importjava.time.format.DateTimeFormatter;@Slf4j@RestController@RequestMapping("/user")publicclassUserController{@AutowiredprivateUserServiceuserService;@AutowiredprivatePatternPropertiespatternProperties;@GetMapping("now")publicStringnow(){returnLocalDateTime.now().format(DateTimeFormatter.ofPattern(patternProperties.getDateformat()));}//略}1.3.配置共享其实微服务启动时,会去nacos读取多个配置文件,例如:[spring.application.name]-[spring.profiles.active].yaml,例如:userservice-dev.yaml[spring.application.name].yaml,例如:userservice.yaml而[spring.application.name].yaml不包含环境,因此可以被多个环境共享。下面我们通过案例来测试配置共享1)添加一个环境共享配置我们在nacos中添加一个userservice.yaml文件:2)在user-service中读取共享配置在user-service服务中,修改PatternProperties类,读取新添加的属性:在user-service服务中,修改UserController,添加一个方法:3)运行两个UserApplication,使用不同的profile修改UserApplication2这个启动项,改变其profile值:这样,UserApplication(8081)使用的profile是dev,UserApplication2(8082)使用的profile是test。启动UserApplication和UserApplication2访问http://localhost:8081/user/prop,结果:访问http://localhost:8082/user/prop,结果:可以看出来,不管是dev,还是test环境,都读取到了envSharedValue这个属性的值。4)配置共享的优先级当nacos、服务本地同时出现相同属性时,优先级有高低之分:Nacos集群搭建1.集群结构图官方给出的Nacos集群图:其中包含3个nacos节点,然后一个负载均衡器代理3个Nacos。这里负载均衡器可以使用nginx。我们计划的集群结构:三个nacos节点的地址:节点ipportnacos1192.168.150.18845nacos2192.168.150.18846nacos3192.168.150.188472.搭建集群搭建集群的基本步骤:搭建数据库,初始化数据库表结构下载nacos安装包配置nacos启动nacos集群nginx反向代理2.1.初始化数据库Nacos默认数据存储在内嵌数据库Derby中,不属于生产可用的数据库。官方推荐的最佳实践是使用带有主从的高可用数据库集群,主从模式的高可用数据库可以参考传智教育的后续高手课程。这里我们以单点的数据库为例来讲解。首先新建一个数据库,命名为nacos,而后导入下面的SQL:CREATETABLE`config_info`(`id`bigint(20)NOTNULLAUTO_INCREMENTCOMMENT'id',`data_id`varchar(255)NOTNULLCOMMENT'data_id',`group_id`varchar(255)DEFAULTNULL,`content`longtextNOTNULLCOMMENT'content',`md5`varchar(32)DEFAULTNULLCOMMENT'md5',`gmt_create`datetimeNOTNULLDEFAULTCURRENT_TIMESTAMPCOMMENT'创建时间',`gmt_modified`datetimeNOTNULLDEFAULTCURRENT_TIMESTAMPCOMMENT'修改时间',`src_user`textCOMMENT'sourceuser',`src_ip`varchar(50)DEFAULTNULLCOMMENT'sourceip',`app_name`varchar(128)DEFAULTNULL,`tenant_id`varchar(128)DEFAULT''COMMENT'租户字段',`c_desc`varchar(256)DEFAULTNULL,`c_use`varchar(64)DEFAULTNULL,`effect`varchar(64)DEFAULTNULL,`type`varchar(64)DEFAULTNULL,`c_schema`text,PRIMARYKEY(`id`),UNIQUEKEY`uk_configinfo_datagrouptenant`(`data_id`,`group_id`,`tenant_id`))ENGINE=InnoDBDEFAULTCHARSET=utf8COLLATE=utf8_binCOMMENT='config_info';/******************************************//*数据库全名=nacos_config*//*表名称=config_info_aggr*//******************************************/CREATETABLE`config_info_aggr`(`id`bigint(20)NOTNULLAUTO_INCREMENTCOMMENT'id',`data_id`varchar(255)NOTNULLCOMMENT'data_id',`group_id`varchar(255)NOTNULLCOMMENT'group_id',`datum_id`varchar(255)NOTNULLCOMMENT'datum_id',`content`longtextNOTNULLCOMMENT'内容',`gmt_modified`datetimeNOTNULLCOMMENT'修改时间',`app_name`varchar(128)DEFAULTNULL,`tenant_id`varchar(128)DEFAULT''COMMENT'租户字段',PRIMARYKEY(`id`),UNIQUEKEY`uk_configinfoaggr_datagrouptenantdatum`(`data_id`,`group_id`,`tenant_id`,`datum_id`))ENGINE=InnoDBDEFAULTCHARSET=utf8COLLATE=utf8_binCOMMENT='增加租户字段';/******************************************//*数据库全名=nacos_config*//*表名称=config_info_beta*//******************************************/CREATETABLE`config_info_beta`(`id`bigint(20)NOTNULLAUTO_INCREMENTCOMMENT'id',`data_id`varchar(255)NOTNULLCOMMENT'data_id',`group_id`varchar(128)NOTNULLCOMMENT'group_id',`app_name`varchar(128)DEFAULTNULLCOMMENT'app_name',`content`longtextNOTNULLCOMMENT'content',`beta_ips`varchar(1024)DEFAULTNULLCOMMENT'betaIps',`md5`varchar(32)DEFAULTNULLCOMMENT'md5',`gmt_create`datetimeNOTNULLDEFAULTCURRENT_TIMESTAMPCOMMENT'创建时间',`gmt_modified`datetimeNOTNULLDEFAULTCURRENT_TIMESTAMPCOMMENT'修改时间',`src_user`textCOMMENT'sourceuser',`src_ip`varchar(50)DEFAULTNULLCOMMENT'sourceip',`tenant_id`varchar(128)DEFAULT''COMMENT'租户字段',PRIMARYKEY(`id`),UNIQUEKEY`uk_configinfobeta_datagrouptenant`(`data_id`,`group_id`,`tenant_id`))ENGINE=InnoDBDEFAULTCHARSET=utf8COLLATE=utf8_binCOMMENT='config_info_beta';/******************************************//*数据库全名=nacos_config*//*表名称=config_info_tag*//******************************************/CREATETABLE`config_info_tag`(`id`bigint(20)NOTNULLAUTO_INCREMENTCOMMENT'id',`data_id`varchar(255)NOTNULLCOMMENT'data_id',`group_id`varchar(128)NOTNULLCOMMENT'group_id',`tenant_id`varchar(128)DEFAULT''COMMENT'tenant_id',`tag_id`varchar(128)NOTNULLCOMMENT'tag_id',`app_name`varchar(128)DEFAULTNULLCOMMENT'app_name',`content`longtextNOTNULLCOMMENT'content',`md5`varchar(32)DEFAULTNULLCOMMENT'md5',`gmt_create`datetimeNOTNULLDEFAULTCURRENT_TIMESTAMPCOMMENT'创建时间',`gmt_modified`datetimeNOTNULLDEFAULTCURRENT_TIMESTAMPCOMMENT'修改时间',`src_user`textCOMMENT'sourceuser',`src_ip`varchar(50)DEFAULTNULLCOMMENT'sourceip',PRIMARYKEY(`id`),UNIQUEKEY`uk_configinfotag_datagrouptenanttag`(`data_id`,`group_id`,`tenant_id`,`tag_id`))ENGINE=InnoDBDEFAULTCHARSET=utf8COLLATE=utf8_binCOMMENT='config_info_tag';/******************************************//*数据库全名=nacos_config*//*表名称=config_tags_relation*//******************************************/CREATETABLE`config_tags_relation`(`id`bigint(20)NOTNULLCOMMENT'id',`tag_name`varchar(128)NOTNULLCOMMENT'tag_name',`tag_type`varchar(64)DEFAULTNULLCOMMENT'tag_type',`data_id`varchar(255)NOTNULLCOMMENT'data_id',`group_id`varchar(128)NOTNULLCOMMENT'group_id',`tenant_id`varchar(128)DEFAULT''COMMENT'tenant_id',`nid`bigint(20)NOTNULLAUTO_INCREMENT,PRIMARYKEY(`nid`),UNIQUEKEY`uk_configtagrelation_configidtag`(`id`,`tag_name`,`tag_type`),KEY`idx_tenant_id`(`tenant_id`))ENGINE=InnoDBDEFAULTCHARSET=utf8COLLATE=utf8_binCOMMENT='config_tag_relation';/******************************************//*数据库全名=nacos_config*//*表名称=group_capacity*//******************************************/CREATETABLE`group_capacity`(`id`bigint(20)unsignedNOTNULLAUTO_INCREMENTCOMMENT'主键ID',`group_id`varchar(128)NOTNULLDEFAULT''COMMENT'GroupID,空字符表示整个集群',`quota`int(10)unsignedNOTNULLDEFAULT'0'COMMENT'配额,0表示使用默认值',`usage`int(10)unsignedNOTNULLDEFAULT'0'COMMENT'使用量',`max_size`int(10)unsignedNOTNULLDEFAULT'0'COMMENT'单个配置大小上限,单位为字节,0表示使用默认值',`max_aggr_count`int(10)unsignedNOTNULLDEFAULT'0'COMMENT'聚合子配置最大个数,,0表示使用默认值',`max_aggr_size`int(10)unsignedNOTNULLDEFAULT'0'COMMENT'单个聚合数据的子配置大小上限,单位为字节,0表示使用默认值',`max_history_count`int(10)unsignedNOTNULLDEFAULT'0'COMMENT'最大变更历史数量',`gmt_create`datetimeNOTNULLDEFAULTCURRENT_TIMESTAMPCOMMENT'创建时间',`gmt_modified`datetimeNOTNULLDEFAULTCURRENT_TIMESTAMPCOMMENT'修改时间',PRIMARYKEY(`id`),UNIQUEKEY`uk_group_id`(`group_id`))ENGINE=InnoDBDEFAULTCHARSET=utf8COLLATE=utf8_binCOMMENT='集群、各Group容量信息表';/******************************************//*数据库全名=nacos_config*//*表名称=his_config_info*//******************************************/CREATETABLE`his_config_info`(`id`bigint(64)unsignedNOTNULL,`nid`bigint(20)unsignedNOTNULLAUTO_INCREMENT,`data_id`varchar(255)NOTNULL,`group_id`varchar(128)NOTNULL,`app_name`varchar(128)DEFAULTNULLCOMMENT'app_name',`content`longtextNOTNULL,`md5`varchar(32)DEFAULTNULL,`gmt_create`datetimeNOTNULLDEFAULTCURRENT_TIMESTAMP,`gmt_modified`datetimeNOTNULLDEFAULTCURRENT_TIMESTAMP,`src_user`text,`src_ip`varchar(50)DEFAULTNULL,`op_type`char(10)DEFAULTNULL,`tenant_id`varchar(128)DEFAULT''COMMENT'租户字段',PRIMARYKEY(`nid`),KEY`idx_gmt_create`(`gmt_create`),KEY`idx_gmt_modified`(`gmt_modified`),KEY`idx_did`(`data_id`))ENGINE=InnoDBDEFAULTCHARSET=utf8COLLATE=utf8_binCOMMENT='多租户改造';/******************************************//*数据库全名=nacos_config*//*表名称=tenant_capacity*//******************************************/CREATETABLE`tenant_capacity`(`id`bigint(20)unsignedNOTNULLAUTO_INCREMENTCOMMENT'主键ID',`tenant_id`varchar(128)NOTNULLDEFAULT''COMMENT'TenantID',`quota`int(10)unsignedNOTNULLDEFAULT'0'COMMENT'配额,0表示使用默认值',`usage`int(10)unsignedNOTNULLDEFAULT'0'COMMENT'使用量',`max_size`int(10)unsignedNOTNULLDEFAULT'0'COMMENT'单个配置大小上限,单位为字节,0表示使用默认值',`max_aggr_count`int(10)unsignedNOTNULLDEFAULT'0'COMMENT'聚合子配置最大个数',`max_aggr_size`int(10)unsignedNOTNULLDEFAULT'0'COMMENT'单个聚合数据的子配置大小上限,单位为字节,0表示使用默认值',`max_history_count`int(10)unsignedNOTNULLDEFAULT'0'COMMENT'最大变更历史数量',`gmt_create`datetimeNOTNULLDEFAULTCURRENT_TIMESTAMPCOMMENT'创建时间',`gmt_modified`datetimeNOTNULLDEFAULTCURRENT_TIMESTAMPCOMMENT'修改时间',PRIMARYKEY(`id`),UNIQUEKEY`uk_tenant_id`(`tenant_id`))ENGINE=InnoDBDEFAULTCHARSET=utf8COLLATE=utf8_binCOMMENT='租户容量信息表';CREATETABLE`tenant_info`(`id`bigint(20)NOTNULLAUTO_INCREMENTCOMMENT'id',`kp`varchar(128)NOTNULLCOMMENT'kp',`tenant_id`varchar(128)default''COMMENT'tenant_id',`tenant_name`varchar(128)default''COMMENT'tenant_name',`tenant_desc`varchar(256)DEFAULTNULLCOMMENT'tenant_desc',`create_source`varchar(32)DEFAULTNULLCOMMENT'create_source',`gmt_create`bigint(20)NOTNULLCOMMENT'创建时间',`gmt_modified`bigint(20)NOTNULLCOMMENT'修改时间',PRIMARYKEY(`id`),UNIQUEKEY`uk_tenant_info_kptenantid`(`kp`,`tenant_id`),KEY`idx_tenant_id`(`tenant_id`))ENGINE=InnoDBDEFAULTCHARSET=utf8COLLATE=utf8_binCOMMENT='tenant_info';CREATETABLE`users`(`username`varchar(50)NOTNULLPRIMARYKEY,`password`varchar(500)NOTNULL,`enabled`booleanNOTNULL);CREATETABLE`roles`(`username`varchar(50)NOTNULL,`role`varchar(50)NOTNULL,UNIQUEINDEX`idx_user_role`(`username`ASC,`role`ASC)USINGBTREE);CREATETABLE`permissions`(`role`varchar(50)NOTNULL,`resource`varchar(255)NOTNULL,`action`varchar(8)NOTNULL,UNIQUEINDEX`uk_role_permission`(`role`,`resource`,`action`)USINGBTREE);INSERTINTOusers(username,password,enabled)VALUES('nacos','$2a$10$EuWPZHzz32dJN7jexM34MOeYirDdFAZm2kuWj7VEOJhhZkDrxfvUu',TRUE);INSERTINTOroles(username,role)VALUES('nacos','ROLE_ADMIN');2.2.下载nacosnacos在GitHub上有下载地址:https://github.com/alibaba/nacos/tags,可以选择任意版本下载。本例中才用1.4.1版本:2.3.配置Nacos将这个包解压到任意非中文目录下,如图:目录说明:bin:启动脚本conf:配置文件进入nacos的conf目录,修改配置文件cluster.conf.example,重命名为cluster.conf:然后添加内容:127.0.0.1:8845127.0.0.1.8846127.0.0.1.8847然后修改application.properties文件,添加数据库配置spring.datasource.platform=mysqldb.num=1db.url.0=jdbc:mysql://127.0.0.1:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTCdb.user.0=rootdb.password.0=1232.4.启动将nacos文件夹复制三份,分别命名为:nacos1、nacos2、nacos3然后分别修改三个文件夹中的application.properties,nacos1:server.port=8845nacos2:server.port=8846nacos3:server.port=8847然后分别启动三个nacos节点:startup.cmd2.5.nginx反向代理安装nginxnginx安装笔记修改conf/nginx.conf文件,配置如下:upstreamnacos-cluster{server127.0.0.1:8845;server127.0.0.1:8846;server127.0.0.1:8847;}server{listen80;server_namelocalhost;location/nacos{proxy_passhttp://nacos-cluster;}}而后在浏览器访问:http://localhost/nacos即可。代码中application.yml文件配置如下:spring:cloud:nacos:server-addr:localhost:80#Nacos地址2.6.优化实际部署时,需要给做反向代理的nginx服务器设置一个域名,这样后续如果有服务器迁移nacos的客户端也无需更改配置.Nacos的各个节点应该部署到多个不同服务器,做好容灾和隔离

Spring Cloud微服务 36 1月前
Spring Cloud微服务的使用与nacos注册中心
大苹果

Spring Cloud微服务的使用与nacos注册中心

SpringCloud微服务1.认识微服务随着互联网行业的发展,对服务的要求也越来越高,服务架构也从单体架构逐渐演变为现在流行的微服务架构。这些架构之间有怎样的差别呢?1.0.学习目标了解微服务架构的优缺点1.1.单体架构单体架构:将业务的所有功能集中在一个项目中开发,打成一个包部署。单体架构的优缺点如下:优点:架构简单部署成本低缺点:耦合度高(维护困难、升级困难)1.2.分布式架构分布式架构:根据业务功能对系统做拆分,每个业务功能模块作为独立项目开发,称为一个服务。分布式架构的优缺点:优点:降低服务耦合有利于服务升级和拓展缺点:服务调用关系错综复杂分布式架构虽然降低了服务耦合,但是服务拆分时也有很多问题需要思考:服务拆分的粒度如何界定?服务之间如何调用?服务的调用关系如何管理?人们需要制定一套行之有效的标准来约束分布式架构。1.3.微服务微服务的架构特征:单一职责:微服务拆分粒度更小,每一个服务都对应唯一的业务能力,做到单一职责自治:团队独立、技术独立、数据独立,独立部署和交付面向服务:服务提供统一标准的接口,与语言和技术无关隔离性强:服务调用做好隔离、容错、降级,避免出现级联问题微服务的上述特性其实是在给分布式架构制定一个标准,进一步降低服务之间的耦合度,提供服务的独立性和灵活性。做到高内聚,低耦合。因此,可以认为微服务是一种经过良好架构设计的分布式架构方案。但方案该怎么落地?选用什么样的技术栈?全球的互联网公司都在积极尝试自己的微服务落地方案。其中在Java领域最引人注目的就是SpringCloud提供的方案了。1.4.SpringCloudSpringCloud是目前国内使用最广泛的微服务框架。官网地址:https://spring.io/projects/spring-cloud。SpringCloud集成了各种微服务功能组件,并基于SpringBoot实现了这些组件的自动装配,从而提供了良好的开箱即用体验。其中常见的组件包括:另外,SpringCloud底层是依赖于SpringBoot的,并且有版本的兼容关系,如下:我们课堂学习的版本是Hoxton.SR10,因此对应的SpringBoot版本是2.3.x版本。1.5.总结单体架构:简单方便,高度耦合,扩展性差,适合小型项目。例如:学生管理系统分布式架构:松耦合,扩展性好,但架构复杂,难度大。适合大型互联网项目,例如:京东、淘宝微服务:一种良好的分布式架构方案①优点:拆分粒度更小、服务更独立、耦合度更低②缺点:架构非常复杂,运维、监控、部署难度提高SpringCloud是微服务架构的一站式解决方案,集成了各种优秀微服务功能组件2.服务拆分和远程调用任何分布式架构都离不开服务的拆分,微服务也是一样。2.1.服务拆分原则这里我总结了微服务拆分时的几个原则:不同微服务,不要重复开发相同业务微服务数据独立,不要访问其它微服务的数据库微服务可以将自己的业务暴露为接口,供其它微服务调用2.2.服务拆分示例以课前资料中的微服务cloud-demo为例,其结构如下:cloud-demo:父工程,管理依赖order-service:订单微服务,负责订单相关业务user-service:用户微服务,负责用户相关业务要求:订单微服务和用户微服务都必须有各自的数据库,相互独立订单服务和用户服务都对外暴露Restful的接口订单服务如果需要查询用户信息,只能调用用户服务的Restful接口,不能查询用户数据库2.2.1.导入Sql语句首先,将课前资料提供的cloud-order.sql和cloud-user.sql导入到mysql中:cloud-user表中初始数据如下:cloud-order表中初始数据如下:cloud-order表中持有cloud-user表中的id字段。2.2.2.导入demo工程用IDEA导入课前资料提供的Demo:项目结构如下:导入后,会在IDEA右下角出现弹窗:点击弹窗,然后按下图选择:会出现这样的菜单:配置下项目使用的JDK:2.3.实现远程调用案例在order-service服务中,有一个根据id查询订单的接口:根据id查询订单,返回值是Order对象,如图:其中的user为null在user-service中有一个根据id查询用户的接口:查询的结果如图:2.3.1.案例需求:修改order-service中的根据id查询订单业务,要求在查询订单的同时,根据订单中包含的userId查询出用户信息,一起返回。因此,我们需要在order-service中向user-service发起一个http的请求,调用http://localhost:8081/user/{userId}这个接口。大概的步骤是这样的:注册一个RestTemplate的实例到Spring容器修改order-service服务中的OrderService类中的queryOrderById方法,根据Order对象中的userId查询User将查询的User填充到Order对象,一起返回2.3.2.注册RestTemplate首先,我们在order-service服务中的OrderApplication启动类中,注册RestTemplate实例:packagecn.itcast.order;importorg.mybatis.spring.annotation.MapperScan;importorg.springframework.boot.SpringApplication;importorg.springframework.boot.autoconfigure.SpringBootApplication;importorg.springframework.context.annotation.Bean;importorg.springframework.web.client.RestTemplate;@MapperScan("cn.itcast.order.mapper")@SpringBootApplicationpublicclassOrderApplication{publicstaticvoidmain(String[]args){SpringApplication.run(OrderApplication.class,args);}@BeanpublicRestTemplaterestTemplate(){returnnewRestTemplate();}}2.3.3.实现远程调用修改order-service服务中的cn.itcast.order.service包下的OrderService类中的queryOrderById方法:2.4.提供者与消费者在服务调用关系中,会有两个不同的角色:服务提供者:一次业务中,被其它微服务调用的服务。(提供接口给其它微服务)服务消费者:一次业务中,调用其它微服务的服务。(调用其它微服务提供的接口)但是,服务提供者与服务消费者的角色并不是绝对的,而是相对于业务而言。如果服务A调用了服务B,而服务B又调用了服务C,服务B的角色是什么?对于A调用B的业务而言:A是服务消费者,B是服务提供者对于B调用C的业务而言:B是服务消费者,C是服务提供者因此,服务B既可以是服务提供者,也可以是服务消费者。3.Eureka注册中心假如我们的服务提供者user-service部署了多个实例,如图:大家思考几个问题:order-service在发起远程调用的时候,该如何得知user-service实例的ip地址和端口?有多个user-service实例地址,order-service调用时该如何选择?order-service如何得知某个user-service实例是否依然健康,是不是已经宕机?3.1.Eureka的结构和作用这些问题都需要利用SpringCloud中的注册中心来解决,其中最广为人知的注册中心就是Eureka,其结构如下:回答之前的各个问题。问题1:order-service如何得知user-service实例地址?获取地址信息的流程如下:user-service服务实例启动后,将自己的信息注册到eureka-server(Eureka服务端)。这个叫服务注册eureka-server保存服务名称到服务实例地址列表的映射关系order-service根据服务名称,拉取实例地址列表。这个叫服务发现或服务拉取问题2:order-service如何从多个user-service实例中选择具体的实例?order-service从实例列表中利用负载均衡算法选中一个实例地址向该实例地址发起远程调用问题3:order-service如何得知某个user-service实例是否依然健康,是不是已经宕机?user-service会每隔一段时间(默认30秒)向eureka-server发起请求,报告自己状态,称为心跳当超过一定时间没有发送心跳时,eureka-server会认为微服务实例故障,将该实例从服务列表中剔除order-service拉取服务时,就能将故障实例排除了注意:一个微服务,既可以是服务提供者,又可以是服务消费者,因此eureka将服务注册、服务发现等功能统一封装到了eureka-client端因此,接下来我们动手实践的步骤包括:3.2.搭建eureka-server首先大家注册中心服务端:eureka-server,这必须是一个独立的微服务3.2.1.创建eureka-server服务在cloud-demo父工程下,创建一个子模块:填写模块信息:然后填写服务信息:3.2.2.引入eureka依赖引入SpringCloud为eureka提供的starter依赖:<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-server</artifactId></dependency>3.2.3.编写启动类给eureka-server服务编写一个启动类,一定要添加一个@EnableEurekaServer注解,开启eureka的注册中心功能:packagecn.itcast.eureka;importorg.springframework.boot.SpringApplication;importorg.springframework.boot.autoconfigure.SpringBootApplication;importorg.springframework.cloud.netflix.eureka.server.EnableEurekaServer;@SpringBootApplication@EnableEurekaServerpublicclassEurekaApplication{publicstaticvoidmain(String[]args){SpringApplication.run(EurekaApplication.class,args);}}3.2.4.编写配置文件编写一个application.yml文件,内容如下:server:port:10086spring:application:name:eureka-servereureka:client:service-url:defaultZone:http://127.0.0.1:10086/eureka3.2.5.启动服务启动微服务,然后在浏览器访问:http://127.0.0.1:10086看到下面结果应该是成功了:3.3.服务注册下面,我们将user-service注册到eureka-server中去。1)引入依赖在user-service的pom文件中,引入下面的eureka-client依赖:<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-client</artifactId></dependency>2)配置文件在user-service中,修改application.yml文件,添加服务名称、eureka地址:spring:application:name:userserviceeureka:client:service-url:defaultZone:http://127.0.0.1:10086/eureka3)启动多个user-service实例为了演示一个服务有多个实例的场景,我们添加一个SpringBoot的启动配置,再启动一个user-service。首先,复制原来的user-service启动配置:然后,在弹出的窗口中,填写信息:现在,SpringBoot窗口会出现两个user-service启动配置:不过,第一个是8081端口,第二个是8082端口。启动两个user-service实例:查看eureka-server管理页面:3.4.服务发现下面,我们将order-service的逻辑修改:向eureka-server拉取user-service的信息,实现服务发现。1)引入依赖之前说过,服务发现、服务注册统一都封装在eureka-client依赖,因此这一步与服务注册时一致。在order-service的pom文件中,引入下面的eureka-client依赖:<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-client</artifactId></dependency>2)配置文件服务发现也需要知道eureka地址,因此第二步与服务注册一致,都是配置eureka信息:在order-service中,修改application.yml文件,添加服务名称、eureka地址:spring:application:name:orderserviceeureka:client:service-url:defaultZone:http://127.0.0.1:10086/eureka3)服务拉取和负载均衡最后,我们要去eureka-server中拉取user-service服务的实例列表,并且实现负载均衡。不过这些动作不用我们去做,只需要添加一些注解即可。在order-service的OrderApplication中,给RestTemplate这个Bean添加一个@LoadBalanced注解:修改order-service服务中的cn.itcast.order.service包下的OrderService类中的queryOrderById方法。修改访问的url路径,用服务名代替ip、端口:spring会自动帮助我们从eureka-server端,根据userservice这个服务名称,获取实例列表,而后完成负载均衡。4.Ribbon负载均衡上一节中,我们添加了@LoadBalanced注解,即可实现负载均衡功能,这是什么原理呢?4.1.负载均衡原理SpringCloud底层其实是利用了一个名为Ribbon的组件,来实现负载均衡功能的。那么我们发出的请求明明是http://userservice/user/1,怎么变成了http://localhost:8081的呢?4.2.源码跟踪为什么我们只输入了service名称就可以访问了呢?之前还要获取ip和端口。显然有人帮我们根据service名称,获取到了服务实例的ip和端口。它就是LoadBalancerInterceptor,这个类会在对RestTemplate的请求进行拦截,然后从Eureka根据服务id获取服务列表,随后利用负载均衡算法得到真实的服务地址信息,替换服务id。我们进行源码跟踪:1)LoadBalancerIntercepor可以看到这里的intercept方法,拦截了用户的HttpRequest请求,然后做了几件事:request.getURI():获取请求uri,本例中就是http://user-service/user/8originalUri.getHost():获取uri路径的主机名,其实就是服务id,user-servicethis.loadBalancer.execute():处理服务id,和用户请求。这里的this.loadBalancer是LoadBalancerClient类型,我们继续跟入。2)LoadBalancerClient继续跟入execute方法:代码是这样的:getLoadBalancer(serviceId):根据服务id获取ILoadBalancer,而ILoadBalancer会拿着服务id去eureka中获取服务列表并保存起来。getServer(loadBalancer):利用内置的负载均衡算法,从服务列表中选择一个。本例中,可以看到获取了8082端口的服务放行后,再次访问并跟踪,发现获取的是8081:果然实现了负载均衡。3)负载均衡策略IRule在刚才的代码中,可以看到获取服务使通过一个getServer方法来做负载均衡:我们继续跟入:继续跟踪源码chooseServer方法,发现这么一段代码:我们看看这个rule是谁:这里的rule默认值是一个RoundRobinRule,看类的介绍:这不就是轮询的意思嘛。到这里,整个负载均衡的流程我们就清楚了。4)总结SpringCloudRibbon的底层采用了一个拦截器,拦截了RestTemplate发出的请求,对地址做了修改。用一幅图来总结一下:基本流程如下:拦截我们的RestTemplate请求http://userservice/user/1RibbonLoadBalancerClient会从请求url中获取服务名称,也就是user-serviceDynamicServerListLoadBalancer根据user-service到eureka拉取服务列表eureka返回列表,localhost:8081、localhost:8082IRule利用内置负载均衡规则,从列表中选择一个,例如localhost:8081RibbonLoadBalancerClient修改请求地址,用localhost:8081替代userservice,得到http://localhost:8081/user/1,发起真实请求4.3.负载均衡策略4.3.1.负载均衡策略负载均衡的规则都定义在IRule接口中,而IRule有很多不同的实现类:不同规则的含义如下:内置负载均衡规则类规则描述RoundRobinRule简单轮询服务列表来选择服务器。它是Ribbon默认的负载均衡规则。AvailabilityFilteringRule对以下两种服务器进行忽略:(1)在默认情况下,这台服务器如果3次连接失败,这台服务器就会被设置为“短路”状态。短路状态将持续30秒,如果再次连接失败,短路的持续时间就会几何级地增加。(2)并发数过高的服务器。如果一个服务器的并发连接数过高,配置了AvailabilityFilteringRule规则的客户端也会将其忽略。并发连接数的上限,可以由客户端的..ActiveConnectionsLimit属性进行配置。WeightedResponseTimeRule为每一个服务器赋予一个权重值。服务器响应时间越长,这个服务器的权重就越小。这个规则会随机选择服务器,这个权重值会影响服务器的选择。ZoneAvoidanceRule以区域可用的服务器为基础进行服务器的选择。使用Zone对服务器进行分类,这个Zone可以理解为一个机房、一个机架等。而后再对Zone内的多个服务做轮询。BestAvailableRule忽略那些短路的服务器,并选择并发数较低的服务器。RandomRule随机选择一个可用的服务器。RetryRule重试机制的选择逻辑默认的实现就是ZoneAvoidanceRule,是一种轮询方案4.3.2.自定义负载均衡策略通过定义IRule实现可以修改负载均衡规则,有两种方式:代码方式:在order-service中的OrderApplication类中,定义一个新的IRule:@BeanpublicIRulerandomRule(){returnnewRandomRule();}配置文件方式:在order-service的application.yml文件中,添加新的配置也可以修改规则:userservice:#给某个微服务配置负载均衡规则,这里是userservice服务ribbon:NFLoadBalancerRuleClassName:com.netflix.loadbalancer.RandomRule#负载均衡规则注意,一般用默认的负载均衡规则,不做修改。4.4.饥饿加载Ribbon默认是采用懒加载,即第一次访问时才会去创建LoadBalanceClient,请求时间会很长。而饥饿加载则会在项目启动时创建,降低第一次访问的耗时,通过下面配置开启饥饿加载:ribbon:eager-load:enabled:trueclients:userservice5.Nacos注册中心国内公司一般都推崇阿里巴巴的技术,比如注册中心,SpringCloudAlibaba也推出了一个名为Nacos的注册中心。5.1.认识和安装NacosNacos是阿里巴巴的产品,现在是SpringCloud中的一个组件。相比Eureka功能更加丰富,在国内受欢迎程度较高。安装方式可以参考课前资料《Nacos安装指南.md》5.2.服务注册到nacosNacos是SpringCloudAlibaba的组件,而SpringCloudAlibaba也遵循SpringCloud中定义的服务注册、服务发现规范。因此使用Nacos和使用Eureka对于微服务来说,并没有太大区别。主要差异在于:依赖不同服务地址不同1)引入依赖在cloud-demo父工程的pom文件中的<dependencyManagement>中引入SpringCloudAlibaba的依赖:<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-alibaba-dependencies</artifactId><version>2.2.6.RELEASE</version><type>pom</type><scope>import</scope></dependency>然后在user-service和order-service中的pom文件中引入nacos-discovery依赖:<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency>注意:不要忘了注释掉eureka的依赖。2)配置nacos地址在user-service和order-service的application.yml中添加nacos地址:spring:cloud:nacos:server-addr:localhost:8848注意:不要忘了注释掉eureka的地址3)重启重启微服务后,登录nacos管理页面,可以看到微服务信息:5.3.服务分级存储模型一个服务可以有多个实例,例如我们的user-service,可以有:127.0.0.1:8081127.0.0.1:8082127.0.0.1:8083假如这些实例分布于全国各地的不同机房,例如:127.0.0.1:8081,在上海机房127.0.0.1:8082,在上海机房127.0.0.1:8083,在杭州机房Nacos就将同一机房内的实例划分为一个集群。也就是说,user-service是服务,一个服务可以包含多个集群,如杭州、上海,每个集群下可以有多个实例,形成分级模型,如图:微服务互相访问时,应该尽可能访问同集群实例,因为本地访问速度更快。当本集群内不可用时,才访问其它集群。例如:杭州机房内的order-service应该优先访问同机房的user-service。5.3.1.给user-service配置集群修改user-service的application.yml文件,添加集群配置:spring:cloud:nacos:server-addr:localhost:8848discovery:cluster-name:HZ#集群名称重启两个user-service实例后,我们可以在nacos控制台看到下面结果:我们再次复制一个user-service启动配置,添加属性:-Dserver.port=8083-Dspring.cloud.nacos.discovery.cluster-name=SH配置如图所示:启动UserApplication3后再次查看nacos控制台:5.3.2.同集群优先的负载均衡默认的ZoneAvoidanceRule并不能实现根据同集群优先来实现负载均衡。因此Nacos中提供了一个NacosRule的实现,可以优先从同集群中挑选实例。1)给order-service配置集群信息修改order-service的application.yml文件,添加集群配置:spring:cloud:nacos:server-addr:localhost:8848discovery:cluster-name:HZ#集群名称2)修改负载均衡规则修改order-service的application.yml文件,修改负载均衡规则:userservice:ribbon:NFLoadBalancerRuleClassName:com.alibaba.cloud.nacos.ribbon.NacosRule#负载均衡规则5.4.权重配置实际部署中会出现这样的场景:服务器设备性能有差异,部分实例所在机器性能较好,另一些较差,我们希望性能好的机器承担更多的用户请求。但默认情况下NacosRule是同集群内随机挑选,不会考虑机器的性能问题。因此,Nacos提供了权重配置来控制访问频率,权重越大则访问频率越高。在nacos控制台,找到user-service的实例列表,点击编辑,即可修改权重:在弹出的编辑窗口,修改权重:注意:如果权重修改为0,则该实例永远不会被访问5.5.环境隔离Nacos提供了namespace来实现环境隔离功能。nacos中可以有多个namespacenamespace下可以有group、service等不同namespace之间相互隔离,例如不同namespace的服务互相不可见5.5.1.创建namespace默认情况下,所有service、data、group都在同一个namespace,名为public:我们可以点击页面新增按钮,添加一个namespace:然后,填写表单:就能在页面看到一个新的namespace:5.5.2.给微服务配置namespace给微服务配置namespace只能通过修改配置来实现。例如,修改order-service的application.yml文件:spring:cloud:nacos:server-addr:localhost:8848discovery:cluster-name:HZnamespace:492a7d5d-237b-46a1-a99a-fa8e98e4b0f9#命名空间,填ID重启order-service后,访问控制台,可以看到下面的结果:此时访问order-service,因为namespace不同,会导致找不到userservice,控制台会报错:5.6.Nacos与Eureka的区别Nacos的服务实例分为两种l类型:临时实例:如果实例宕机超过一定时间,会从服务列表剔除,默认的类型。非临时实例:如果实例宕机,不会从服务列表剔除,也可以叫永久实例。配置一个服务实例为永久实例:spring:cloud:nacos:discovery:ephemeral:false#设置为非临时实例Nacos和Eureka整体结构类似,服务注册、服务拉取、心跳等待,但是也存在一些差异:Nacos与eureka的共同点都支持服务注册和服务拉取都支持服务提供者心跳方式做健康检测Nacos与Eureka的区别Nacos支持服务端主动检测提供者状态:临时实例采用心跳模式,非临时实例采用主动检测模式临时实例心跳不正常会被剔除,非临时实例则不会被剔除Nacos支持服务列表变更的消息推送模式,服务列表更新更及时Nacos集群默认采用AP方式,当集群中存在非临时实例时,采用CP模式;Eureka采用AP方式

Spring Cloud微服务 18 1月前
Seata架构、部署、微服务集成
大苹果

Seata架构、部署、微服务集成

Seata的架构SeataSeata是2019年1月份蚂蚁金服和阿里巴巴共同开源的分布式事务解决方案。致力于提供高性能和简单易用的分布式事务服务,为用户打造一站式的分布式解决方案。官网地址:http://seata.io/,其中的文档、播客中提供了大量的使用说明、源码分析。Seata事务管理中有三个重要的角色:TC(TransactionCoordinator)-事务协调者:维护全局和分支事务的状态,协调全局事务提交或回滚。TM(TransactionManager)-事务管理器:定义全局事务的范围、开始全局事务、提交或回滚全局事务。RM(ResourceManager)-资源管理器:管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。Seata提供了四种不同的分布式事务解决方案:XA模式:强一致性分阶段事务模式,牺牲了一定的可用性,无业务侵入TCC模式:最终一致的分阶段事务模式,有业务侵入AT模式:最终一致的分阶段事务模式,无业务侵入,也是Seata的默认模式SAGA模式:长事务模式,有业务侵入部署TC服务seata的部署和集成一、部署Seata的tc-server1.下载首先我们要下载seata-server包,地址在http://seata.io/zh-cn/blog/download.html2.解压在非中文目录解压缩这个zip包,其目录结构如下:3.修改配置修改conf目录下的registry.conf文件:内容如下:registry{#tc服务的注册中心类,这里选择nacos,也可以是eureka、zookeeper等type="nacos"nacos{#seatatc服务注册到nacos的服务名称,可以自定义application="seata-tc-server"serverAddr="127.0.0.1:8848"group="DEFAULT_GROUP"namespace=""cluster="SH"username="nacos"password="nacos"}}config{#读取tc服务端的配置文件的方式,这里是从nacos配置中心读取,这样如果tc是集群,可以共享配置type="nacos"#配置nacos地址等信息nacos{serverAddr="127.0.0.1:8848"namespace=""group="SEATA_GROUP"username="nacos"password="nacos"dataId="seataServer.properties"}}4.在nacos添加配置特别注意,为了让tc服务的集群可以共享配置,我们选择了nacos作为统一配置中心。因此服务端配置文件seataServer.properties文件需要在nacos中配好。格式如下:配置内容如下:#数据存储方式,db代表数据库store.mode=dbstore.db.datasource=druidstore.db.dbType=mysqlstore.db.driverClassName=com.mysql.jdbc.Driverstore.db.url=jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true&rewriteBatchedStatements=truestore.db.user=rootstore.db.password=123store.db.minConn=5store.db.maxConn=30store.db.globalTable=global_tablestore.db.branchTable=branch_tablestore.db.queryLimit=100store.db.lockTable=lock_tablestore.db.maxWait=5000#事务、日志等配置server.recovery.committingRetryPeriod=1000server.recovery.asynCommittingRetryPeriod=1000server.recovery.rollbackingRetryPeriod=1000server.recovery.timeoutRetryPeriod=1000server.maxCommitRetryTimeout=-1server.maxRollbackRetryTimeout=-1server.rollbackRetryTimeoutUnlockEnable=falseserver.undo.logSaveDays=7server.undo.logDeletePeriod=86400000#客户端与服务端传输方式transport.serialization=seatatransport.compressor=none#关闭metrics功能,提高性能metrics.enabled=falsemetrics.registryType=compactmetrics.exporterList=prometheusmetrics.exporterPrometheusPort=9898其中的数据库地址、用户名、密码都需要修改成你自己的数据库信息。5.创建数据库表特别注意:tc服务在管理分布式事务时,需要记录事务相关数据到数据库中,你需要提前创建好这些表。新建一个名为seata的数据库:createdatabaseseataCHARACTERSET=utf8mb4COLLATE=utf8mb4_general_ci;这些表主要记录全局事务、分支事务、全局锁信息:SETNAMESutf8mb4;SETFOREIGN_KEY_CHECKS=0;--------------------------------分支事务表------------------------------DROPTABLEIFEXISTS`branch_table`;CREATETABLE`branch_table`(`branch_id`bigint(20)NOTNULL,`xid`varchar(128)CHARACTERSETutf8COLLATEutf8_general_ciNOTNULL,`transaction_id`bigint(20)NULLDEFAULTNULL,`resource_group_id`varchar(32)CHARACTERSETutf8COLLATEutf8_general_ciNULLDEFAULTNULL,`resource_id`varchar(256)CHARACTERSETutf8COLLATEutf8_general_ciNULLDEFAULTNULL,`branch_type`varchar(8)CHARACTERSETutf8COLLATEutf8_general_ciNULLDEFAULTNULL,`status`tinyint(4)NULLDEFAULTNULL,`client_id`varchar(64)CHARACTERSETutf8COLLATEutf8_general_ciNULLDEFAULTNULL,`application_data`varchar(2000)CHARACTERSETutf8COLLATEutf8_general_ciNULLDEFAULTNULL,`gmt_create`datetime(6)NULLDEFAULTNULL,`gmt_modified`datetime(6)NULLDEFAULTNULL,PRIMARYKEY(`branch_id`)USINGBTREE,INDEX`idx_xid`(`xid`)USINGBTREE)ENGINE=InnoDBCHARACTERSET=utf8COLLATE=utf8_general_ciROW_FORMAT=Compact;--------------------------------全局事务表------------------------------DROPTABLEIFEXISTS`global_table`;CREATETABLE`global_table`(`xid`varchar(128)CHARACTERSETutf8COLLATEutf8_general_ciNOTNULL,`transaction_id`bigint(20)NULLDEFAULTNULL,`status`tinyint(4)NOTNULL,`application_id`varchar(32)CHARACTERSETutf8COLLATEutf8_general_ciNULLDEFAULTNULL,`transaction_service_group`varchar(32)CHARACTERSETutf8COLLATEutf8_general_ciNULLDEFAULTNULL,`transaction_name`varchar(128)CHARACTERSETutf8COLLATEutf8_general_ciNULLDEFAULTNULL,`timeout`int(11)NULLDEFAULTNULL,`begin_time`bigint(20)NULLDEFAULTNULL,`application_data`varchar(2000)CHARACTERSETutf8COLLATEutf8_general_ciNULLDEFAULTNULL,`gmt_create`datetimeNULLDEFAULTNULL,`gmt_modified`datetimeNULLDEFAULTNULL,PRIMARYKEY(`xid`)USINGBTREE,INDEX`idx_gmt_modified_status`(`gmt_modified`,`status`)USINGBTREE,INDEX`idx_transaction_id`(`transaction_id`)USINGBTREE)ENGINE=InnoDBCHARACTERSET=utf8COLLATE=utf8_general_ciROW_FORMAT=Compact;SETFOREIGN_KEY_CHECKS=1;6.启动TC服务进入bin目录,运行其中的seata-server.bat即可:启动成功后,seata-server应该已经注册到nacos注册中心了。打开浏览器,访问nacos地址:http://localhost:8848,然后进入服务列表页面,可以看到seata-tc-server的信息:二、微服务集成seata1.引入依赖首先,我们需要在微服务中引入seata依赖:<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-seata</artifactId><exclusions><!--版本较低,1.3.0,因此排除--><exclusion><artifactId>seata-spring-boot-starter</artifactId><groupId>io.seata</groupId></exclusion></exclusions></dependency><!--seatastarter采用1.4.2版本--><dependency><groupId>io.seata</groupId><artifactId>seata-spring-boot-starter</artifactId><version>${seata.version}</version></dependency>2.修改配置文件需要修改application.yml文件,添加一些配置:seata:registry:#TC服务注册中心的配置,微服务根据这些信息去注册中心获取tc服务地址#参考tc服务自己的registry.conf中的配置type:nacosnacos:#tcserver-addr:127.0.0.1:8848namespace:""group:DEFAULT_GROUPapplication:seata-tc-server#tc服务在nacos中的服务名称cluster:SHtx-service-group:seata-demo#事务组,根据这个获取tc服务的cluster名称service:vgroup-mapping:#事务组与TC服务cluster的映射关系seata-demo:SH三、TC服务的高可用和异地容灾1.模拟异地容灾的TC集群计划启动两台seata的tc服务节点:节点名称ip地址端口号集群名称seata127.0.0.18091SHseata2127.0.0.18092HZ之前我们已经启动了一台seata服务,端口是8091,集群名为SH。现在,将seata目录复制一份,起名为seata2修改seata2/conf/registry.conf内容如下:registry{#tc服务的注册中心类,这里选择nacos,也可以是eureka、zookeeper等type="nacos"nacos{#seatatc服务注册到nacos的服务名称,可以自定义application="seata-tc-server"serverAddr="127.0.0.1:8848"group="DEFAULT_GROUP"namespace=""cluster="HZ"username="nacos"password="nacos"}}config{#读取tc服务端的配置文件的方式,这里是从nacos配置中心读取,这样如果tc是集群,可以共享配置type="nacos"#配置nacos地址等信息nacos{serverAddr="127.0.0.1:8848"namespace=""group="SEATA_GROUP"username="nacos"password="nacos"dataId="seataServer.properties"}}进入seata2/bin目录,然后运行命令:seata-server.bat-p8092打开nacos控制台,查看服务列表:点进详情查看:2.将事务组映射配置到nacosnacos安装与配置接下来,我们需要将tx-service-group与cluster的映射关系都配置到nacos配置中心。新建一个配置:配置的内容如下:#事务组映射关系service.vgroupMapping.seata-demo=SHservice.enableDegrade=falseservice.disableGlobalTransaction=false#与TC服务的通信配置transport.type=TCPtransport.server=NIOtransport.heartbeat=truetransport.enableClientBatchSendRequest=falsetransport.threadFactory.bossThreadPrefix=NettyBosstransport.threadFactory.workerThreadPrefix=NettyServerNIOWorkertransport.threadFactory.serverExecutorThreadPrefix=NettyServerBizHandlertransport.threadFactory.shareBossWorker=falsetransport.threadFactory.clientSelectorThreadPrefix=NettyClientSelectortransport.threadFactory.clientSelectorThreadSize=1transport.threadFactory.clientWorkerThreadPrefix=NettyClientWorkerThreadtransport.threadFactory.bossThreadSize=1transport.threadFactory.workerThreadSize=defaulttransport.shutdown.wait=3#RM配置client.rm.asyncCommitBufferLimit=10000client.rm.lock.retryInterval=10client.rm.lock.retryTimes=30client.rm.lock.retryPolicyBranchRollbackOnConflict=trueclient.rm.reportRetryCount=5client.rm.tableMetaCheckEnable=falseclient.rm.tableMetaCheckerInterval=60000client.rm.sqlParserType=druidclient.rm.reportSuccessEnable=falseclient.rm.sagaBranchRegisterEnable=false#TM配置client.tm.commitRetryCount=5client.tm.rollbackRetryCount=5client.tm.defaultGlobalTransactionTimeout=60000client.tm.degradeCheck=falseclient.tm.degradeCheckAllowTimes=10client.tm.degradeCheckPeriod=2000#undo日志配置client.undo.dataValidation=trueclient.undo.logSerialization=jacksonclient.undo.onlyCareUpdateColumns=trueclient.undo.logTable=undo_logclient.undo.compress.enable=trueclient.undo.compress.type=zipclient.undo.compress.threshold=64kclient.log.exceptionRate=1003.微服务读取nacos配置接下来,需要修改每一个微服务的application.yml文件,让微服务读取nacos中的client.properties文件:seata:config:type:nacosnacos:server-addr:127.0.0.1:8848username:nacospassword:nacosgroup:SEATA_GROUPdata-id:client.properties重启微服务,现在微服务到底是连接tc的SH集群,还是tc的HZ集群,都统一由nacos的client.properties来决定了。nacos服务名称组成包括?namespace+group+serviceName+clusterseata客户端获取tc的cluster名称方式?以tx-group-service的值为key到vgroupMapping中查找

分布式事务Seata 31 1月前
Docker部署seata
大苹果

Docker部署seata

下载seata-serverseata-server解压部署目录结构修改配置文件(conf/application.yml)server:port:7091spring:application:name:seata-servercloud:nacos:discovery:server-addr:nocas-ip:8848ip:${HOST:192.168.40.167}port:8081logging:config:classpath:logback-spring.xmlfile:path:${log.home:${user.home}/logs/seata}extend:logstash-appender:destination:127.0.0.1:4560kafka-appender:bootstrap-servers:127.0.0.1:9092topic:logback_to_logstashconsole:user:username:seatapassword:seataseata:config:#support:nacos,consul,apollo,zk,etcd3type:fileregistry:#support:nacos,eureka,redis,zk,consul,etcd3,sofatype:nacosnacos:#tcserver-addr:nocas-ip:8848namespace:""group:DEFAULT_GROUPapplication:seata-tc-server#tc服务在nacos中的服务名称cluster:SHstore:#support:file、db、redis、raftmode:file#server:#service-port:8091#Ifnotconfigured,thedefaultis'${server.port}+1000'security:secretKey:SeataSecretKey0c382ef121d778043159209298fd40bf3850a017tokenValidityInMilliseconds:1800000ignore:urls:/,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.jpeg,/**/*.ico,/api/v1/auth/login,/metadata/v1/**tar-zxvfseata-server-2.0.0.tar.gzcdseatadockerbuild.--tagseata:v1.0#指定与nacos同网络#SEATA_IP指定公网ipdockerrun-d--networkexample_default--add-host=nocas-ip:192.168.40.167--nameseata-server-p8091:8091-p7091:7091-eSEATA_IP=192.168.40.167seata:v1.0#查看日志dockerlogs-fseata-server客户端数据库创建undo_log表CREATETABLE`undo_log`(`id`bigint(20)NOTNULLAUTO_INCREMENT,`branch_id`bigint(20)NOTNULL,`xid`varchar(100)NOTNULL,`context`varchar(128)NOTNULL,`rollback_info`longblobNOTNULL,`log_status`int(11)NOTNULL,`log_created`datetimeNOTNULL,`log_modified`datetimeNOTNULL,`ext`varchar(100)DEFAULTNULL,PRIMARYKEY(`id`),UNIQUEKEY`ux_undo_log`(`xid`,`branch_id`))ENGINE=InnoDBAUTO_INCREMENT=1DEFAULTCHARSET=utf8;docker-compose部署seatadocker-compose.ymlversion:"3.1"services:seata-server:#镜像image:seataio/seata-server:1.5.2ports:#宿主端口:容器端口-"7092:7091"-"8092:8092"#环境变量environment:-STORE_MODE=db#以SEATA_IP作为host注册seataserver-SEATA_IP=192.168.40.167-SEATA_PORT=8092-STORE_MODE=file#目录挂载volumes:-"/usr/share/zoneinfo/Asia/Shanghai:/etc/localtime"#设置系统时区-"/usr/share/zoneinfo/Asia/Shanghai:/etc/timezone"#设置时区#假设我们通过dockercp命令把资源文件拷贝到相对路径`./seata-server/resources`中#如有问题,请阅读上面的[注意事项]以及[使用自定义配置文件]#-"./seata-server/resources:/seata-server/resources"-"./seata-server/resources:/seata-server/config"#指定网络networks:-example_default#指定已有网络networks:example_default:external:trueapplication.ymlserver:port:7091debug:falsespring:application:name:seata-servercloud:nacos:server-addr:172.22.0.2:8848seata:config:#support:nacos,consul,apollo,zk,etcd3type:file#nacos:#server-addr:172.22.0.2:8848#namespace:#group:DEFAULT_GROUP#username:nacos#password:nacos#data-id:seataServer.propertiesstore:#support:file、db、redis、raftmode:filesecurity:secretKey:SeataSecretKey0c382ef121d778043159209298fd40bf3850a017tokenValidityInMilliseconds:1800000ignore:urls:/,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.jpeg,/**/*.ico,/api/v1/auth/login,/metadata/v1/**registry:#TC服务注册中心的配置,微服务根据这些信息去注册中心获取tc服务地址#参考tc服务自己的registry.conf中的配置type:nacosnacos:#tcserver-addr:172.22.0.2:8848namespace:""group:DEFAULT_GROUPapplication:seata-tc-server#tc服务在nacos中的服务名称cluster:HZ部署dockercomposeup-d

分布式事务Seata 46 1月前
事务模式
大苹果

事务模式

XA模式XA模式原理XA规范是X/Open组织定义的分布式事务处理(DTP,DistributedTransactionProcessing)标准,XA规范描述了全局的TM与局部的RM之间的接口,几乎所有主流的数据库都对XA规范提供了支持。seata的XA模式seata的XA模式做了一些调整,但大体相似:RM一阶段的工作:①注册分支事务到TC②执行分支业务sql但不提交③报告执行状态到TCTC二阶段的工作:TC检测各分支事务执行状态a.如果都成功,通知所有RM提交事务b.如果有失败,通知所有RM回滚事务RM二阶段的工作:接收TC指令,提交或回滚事务XA模式的优点是什么?事务的强一致性,满足ACID原则。常用数据库都支持,实现简单,并且没有代码侵入XA模式的缺点是什么?因为一阶段需要锁定数据库资源,等待二阶段结束才释放,性能较差依赖关系型数据库实现事务实现XA模式Seata的starter已经完成了XA模式的自动装配,实现非常简单,步骤如下:修改application.yml文件(每个参与事务的微服务),开启XA模式:seata:data-source-proxy-mode:XA#开启数据源代理的XA模式给发起全局事务的入口方法添加@GlobalTransactional注解,本例中是OrderServiceImpl中的create方法:@Override@GlobalTransactionalpublicLongcreate(Orderorder){//创建订单orderMapper.insert(order);//扣余额...略//扣减库存...略returnorder.getId();}重启服务并测试AT模式AT模式原理AT模式同样是分阶段提交的事务模型,不过缺弥补了XA模型中资源锁定周期过长的缺陷。阶段一RM的工作:注册分支事务记录undo-log(数据快照)执行业务sql并提交报告事务状态阶段二提交时RM的工作:删除undo-log即可阶段二回滚时RM的工作:根据undo-log恢复数据到更新前例如,一个分支业务的SQL是这样的:updatetb_accountsetmoney=money-10whereid=1简述AT模式与XA模式最大的区别是什么?XA模式一阶段不提交事务,锁定资源;AT模式一阶段直接提交,不锁定资源。XA模式依赖数据库机制实现回滚;AT模式利用数据快照实现数据回滚。XA模式强一致;AT模式最终一致AT模式的脏写问题AT模式的优点:一阶段完成直接提交事务,释放数据库资源,性能比较好利用全局锁实现读写隔离没有代码侵入,框架自动完成回滚和提交AT模式的缺点:两阶段之间属于软状态,属于最终一致框架的快照功能会影响性能,但比XA模式要好很多实现AT模式AT模式中的快照生成、回滚等动作都是由框架自动完成,没有任何代码侵入,因此实现非常简单。导入课前资料提供的Sql文件:seata-at.sql,其中lock_table导入到TC服务关联的数据库,undo_log表导入到微服务关联的数据库:SETNAMESutf8mb4;SETFOREIGN_KEY_CHECKS=0;--------------------------------Tablestructureforundo_log------------------------------DROPTABLEIFEXISTS`undo_log`;CREATETABLE`undo_log`(`branch_id`bigint(20)NOTNULLCOMMENT'branchtransactionid',`xid`varchar(100)CHARACTERSETutf8COLLATEutf8_general_ciNOTNULLCOMMENT'globaltransactionid',`context`varchar(128)CHARACTERSETutf8COLLATEutf8_general_ciNOTNULLCOMMENT'undo_logcontext,suchasserialization',`rollback_info`longblobNOTNULLCOMMENT'rollbackinfo',`log_status`int(11)NOTNULLCOMMENT'0:normalstatus,1:defensestatus',`log_created`datetime(6)NOTNULLCOMMENT'createdatetime',`log_modified`datetime(6)NOTNULLCOMMENT'modifydatetime',UNIQUEINDEX`ux_undo_log`(`xid`,`branch_id`)USINGBTREE)ENGINE=InnoDBCHARACTERSET=utf8COLLATE=utf8_general_ciCOMMENT='ATtransactionmodeundotable'ROW_FORMAT=Compact;--------------------------------Recordsofundo_log--------------------------------------------------------------Tablestructureforlock_table------------------------------DROPTABLEIFEXISTS`lock_table`;CREATETABLE`lock_table`(`row_key`varchar(128)CHARACTERSETutf8COLLATEutf8_general_ciNOTNULL,`xid`varchar(96)CHARACTERSETutf8COLLATEutf8_general_ciNULLDEFAULTNULL,`transaction_id`bigint(20)NULLDEFAULTNULL,`branch_id`bigint(20)NOTNULL,`resource_id`varchar(256)CHARACTERSETutf8COLLATEutf8_general_ciNULLDEFAULTNULL,`table_name`varchar(32)CHARACTERSETutf8COLLATEutf8_general_ciNULLDEFAULTNULL,`pk`varchar(36)CHARACTERSETutf8COLLATEutf8_general_ciNULLDEFAULTNULL,`gmt_create`datetimeNULLDEFAULTNULL,`gmt_modified`datetimeNULLDEFAULTNULL,PRIMARYKEY(`row_key`)USINGBTREE,INDEX`idx_branch_id`(`branch_id`)USINGBTREE)ENGINE=InnoDBCHARACTERSET=utf8COLLATE=utf8_general_ciROW_FORMAT=Compact;SETFOREIGN_KEY_CHECKS=1;修改application.yml文件,将事务模式修改为AT模式即可:seata:data-source-proxy-mode:AT#开启数据源代理的AT模式重启服务并测试TCC模式TCC模式原理TCC模式与AT模式非常相似,每阶段都是独立事务,不同的是TCC通过人工编码来实现数据恢复。需要实现三个方法:Try:资源的检测和预留;Confirm:完成资源操作业务;要求Try成功Confirm一定要能成功。Cancel:预留资源释放,可以理解为try的反向操作。TCC模式的每个阶段是做什么的?Try:资源检查和预留Confirm:业务执行和提交Cancel:预留资源的释放TCC的优点是什么?一阶段完成直接提交事务,释放数据库资源,性能好相比AT模型,无需生成快照,无需使用全局锁,性能最强不依赖数据库事务,而是依赖补偿操作,可以用于非事务型数据库TCC的缺点是什么?有代码侵入,需要人为编写try、Confirm和Cancel接口,太麻烦软状态,事务是最终一致需要考虑Confirm和Cancel的失败情况,做好幂等处理TCC的空回滚和业务悬挂当某分支事务的try阶段阻塞时,可能导致全局事务超时而触发二阶段的cancel操作。在未执行try操作时先执行了cancel操作,这时cancel不能做回滚,就是空回滚。对于已经空回滚的业务,如果以后继续执行try,就永远不可能confirm或cancel,这就是业务悬挂。应当阻止执行空回滚后的try操作,避免悬挂业务分析为了实现空回滚、防止业务悬挂,以及幂等性要求。我们必须在数据库记录冻结金额的同时,记录当前事务id和执行状态,为此我们设计了一张表CREATETABLE`account_freeze_tbl`(`xid`varchar(128)NOTNULL,`user_id`varchar(255)DEFAULTNULLCOMMENT'用户id',`freeze_money`int(11)unsignedDEFAULT'0'COMMENT'冻结金额',`state`int(1)DEFAULTNULLCOMMENT'事务状态,0:try,1:confirm,2:cancel',PRIMARYKEY(`xid`)USINGBTREE)ENGINE=InnoDBDEFAULTCHARSET=utf8ROW_FORMAT=COMPACT;声明TCC接口TCC的Try、Confirm、Cancel方法都需要在接口中基于注解来声明,语法如下:@LocalTCCpublicinterfaceTCCService{/***Try逻辑,@TwoPhaseBusinessAction中的name属性要与当前方法名一致,用于指定Try逻辑对应的方法*/@TwoPhaseBusinessAction(name="prepare",commitMethod="confirm",rollbackMethod="cancel")voidprepare(@BusinessActionContextParameter(paramName="param")Stringparam);/***二阶段confirm确认方法、可以另命名,但要保证与commitMethod一致**@paramcontext上下文,可以传递try方法的参数*@returnboolean执行是否成功*/booleanconfirm(BusinessActionContextcontext);/***二阶段回滚方法,要保证与rollbackMethod一致*/booleancancel(BusinessActionContextcontext);}SAGA模式Saga模式是SEATA提供的长事务解决方案。也分为两个阶段:一阶段:直接提交本地事务二阶段:成功则什么都不做;失败则通过编写补偿业务来回滚Saga模式优点:事务参与者可以基于事件驱动实现异步调用,吞吐高一阶段直接提交事务,无锁,性能好不用编写TCC中的三个阶段,实现简单缺点:软状态持续时间不确定,时效性差没有锁,没有事务隔离,会有脏写四种模式XAATTCCSAGA一致性强一致弱一致弱一致最终一致隔离性完全隔离基于全局锁隔离基于资源预留隔离无隔离代码侵入无无有,要编写三个接口有,要编写状态机和补偿业务性能差好非常好非常好场景对一致性、隔离性有高要求的业务基于关系型数据库的大多数分布式事务场景都可以•对性能要求较高的事务。•有非关系型数据库要参与的事务。•业务流程长、业务流程多•参与者包含其它公司或遗留系统服务,无法提供TCC模式要求的三个接口

分布式事务Seata 29 1月前
Base理论
大苹果

Base理论

BASE理论BASE理论是对CAP的一种解决思路,包含三个思想:BasicallyAvailable(基本可用):分布式系统在出现故障时,允许损失部分可用性,即保证核心可用。SoftState(软状态):在一定时间内,允许出现中间状态,比如临时的不一致状态。EventuallyConsistent(最终一致性):虽然无法保证强一致性,但是在软状态结束后,最终达到数据一致。而分布式事务最大的问题是各个子事务的一致性问题,因此可以借鉴CAP定理和BASE理论:AP模式:各子事务分别执行和提交,允许出现结果不一致,然后采用弥补措施恢复数据即可,实现最终一致。CP模式:各个子事务执行后互相等待,同时提交,同时回滚,达成强一致。但事务等待过程中,处于弱可用状态。分布式事务模型解决分布式事务,各个子系统之间必须能感知到彼此的事务状态,才能保证状态一致,因此需要一个事务协调者来协调每一个事务的参与者(子系统事务)。这里的子系统事务,称为分支事务;有关联的各个分支事务在一起称为全局事务简述BASE理论三个思想:基本可用软状态最终一致解决分布式事务的思想和模型:全局事务:整个分布式事务分支事务:分布式事务中包含的每个子系统的事务最终一致思想:各分支事务分别执行并提交,如果有不一致的情况,再想办法恢复数据强一致思想:各分支事务执行完业务不要提交,等待彼此结果。而后统一提交或回滚

分布式事务Seata 17 1月前
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 >> 共 18 页