本文共 21360 字,大约阅读时间需要 71 分钟。
单机架构:
优点:便于测试,对小项目友好
缺点:开发速度慢,启动时间长,依赖庞大
分布式架构:
SOA:面向服务的架构 其中包含多个服务,服务之间通过相互依赖提供一系类功能,一个服务通常以独立的形式存在于操作系统中
微服务:将一个大的【单体项目】拆分成多个子项目和服务,每个拆分出来的服务各自独立打包部署
优点:便于理解、开发、部署
缺点:存在分布式事务、服务治理
Spring Alibaba Cloud
module子模块
xdclass-commonxdclass-video-servicexdclass-user-servicexdclass-order-servicexdclass-api-gateway
4.0.0 cn.mesmile xdclass-cloud 1.0-SNAPSHOT xdclass-common xdclass-video-service xdclass-user-service xdclass-order-service xdclass-api-gateway pom 1.8 1.8 1.8 org.projectlombok lombok 1.18.8 provided cn.hutool hutool-all 5.4.0 org.springframework.boot spring-boot-dependencies 2.3.3.RELEASE pom import org.springframework.cloud spring-cloud-dependencies Hoxton.SR8 pom import com.alibaba.cloud spring-cloud-alibaba-dependencies 2.2.1.RELEASE pom import org.mybatis.spring.boot mybatis-spring-boot-starter 2.1.2 pom import org.springframework.boot spring-boot-maven-plugin true true
子项目依赖:
org.springframework.boot spring-boot-starter-web cn.mesmile xdclass-common 1.0-SNAPSHOT org.mybatis.spring.boot mybatis-spring-boot-starter mysql mysql-connector-java com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery com.alibaba.cloud spring-cloud-starter-alibaba-nacos-config org.springframework.cloud spring-cloud-starter-openfeign com.alibaba.cloud spring-cloud-starter-alibaba-sentinel org.springframework.cloud spring-cloud-starter-sleuth org.springframework.cloud spring-cloud-starter-zipkin
服务之间ip写死了
服务之间服务提供负载均衡
多个服务之间调用复杂
服务注册 与 服务发现
主流的注册中心:zookeeper、Eureka、consul、etcd、Nacos
这里使用nacos作为注册中心:
nacos官方文档:https://nacos.io/zh-cn/docs/quick-start.html
注意nacos在window中安装的坑:
1.需要修改配置文件中 MySQL 的设置
2.需要修改配置文件中 将 集群模式 改为 单机模式
1.第一个问题
2.第二个问题
nacos注册中心使用:
nacos默认的地址:http://127.0.0.1:8848/nacos
nacos默认的账号和密码:nacos/nacos
配置文件:
# application 的名字spring: application: name: xdclass-video-service cloud: nacos: discovery: server-addr: 127.0.0.1:8848
在启动类加上注解:
@EnableDiscoveryClient
// 可以服务之间调用private final DiscoveryClient discoveryClient;// 这里的参数默认为,配置文件中的 application.name = xdclass-video-serviceListinstances = discoveryClient.getInstances("xdclass-video-service");ServiceInstance serviceInstance = instances.get(0);String host = serviceInstance.getHost();int port = serviceInstance.getPort();
Load Balance
常见的负载均衡策略(看组件的支持情况)
restTemplate增加 **@LoadBalanced ** 注解
/** * @LoadBalanced ribbon 负载均衡 默认是 轮训的策略 */@Bean@LoadBalancedpublic RestTemplate getRestTemplate () { return new RestTemplate();}
这样通过 restTemplate 的方式调用的话,就可以实现负载均衡 : 默认策略是【 **节点轮询 **】
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qHAwo33a-1606960678010)(C:\Users\AOSSCI\AppData\Roaming\Typora\typora-user-images\image-20201026110008372.png)]
配置文件:
# video 服务 IRule 规则; 【改变负载均衡策略】 默认为轮训策略xdclass-video-service: ribbon: NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
Feign默认集成了Ribbon
org.springframework.cloud spring-cloud-starter-openfeign
启动类配置注解:
@EnableFeignClients
增加一个接口:
@FeignClient(value = "xdclass-video-service", fallback = VideoServiceFallback.class)public interface VideoService { // 这里的方法是 video 视频服务中的方法 @GetMapping("/api/v1/video/getVideoById") Video getVideoById (@RequestParam("videoId")Integer videoId);}// value 是application.name fallback 的参数是指定的一个 托底的类,当这个接口中的方法失效的时候,会进入 回调类中调用托底方法@FeignClient(value = "xdclass-video-service", fallback = VideoServiceFallback.class)
托底类:
@Servicepublic class VideoServiceFallback implements VideoService { // 正常接口调用失败,就会进入托底类中调用此方法 @Override public Video getVideoById(Integer videoId) { Video video = new Video(); video.setTitle("这是一个兜底 fallback 数据"); video.setId(3); video.setCoverImg("images"); video.setSummary("描述"); return video; }}
ribbon和feign两个的区别和选择:
选择feign,feign默认集成了ribbon写起来更加思路清晰和方便,feign面向接口编程采用注解方式进行配置,配置熔断等方式方便
CAP定理: 指的是在一个分布式系统中,Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性),三者不可同时获得
CAP理论就是说在分布式存储系统中,最多只能实现上面的两点。而由于当前的网络硬件肯定会出现延迟丢包等问题,所以分区容忍性是我们必须需要实现的。所以我们只能在一致性和可用性之间进行权衡
CA: 如果不要求P(不允许分区),则C(强一致性)和A(可用性)是可以保证的。但放弃P的同时也就意味着放弃了系统的扩展性,也就是分布式节点受限,没办法部署子节点,这是违背分布式系统设计的初衷的CP: 如果不要求A(可用),每个请求都需要在服务器之间保持强一致,而P(分区)会导致同步时间无限延长(也就是等待数据同步完才能正常访问服务),一旦发生网络故障或者消息丢失等情况,就要牺牲用户的体验,等待所有数据全部一致了之后再让用户访问系统AP:要高可用并允许分区,则需放弃一致性。一旦分区发生,节点之间可能会失去联系,为了高可用,每个节点只能用本地数据提供服务,而这样会导致全局数据的不一致性。
Nacos | Eureka | Consul | Zookeeper | |
---|---|---|---|---|
一致性协议 | CP+AP | AP | CP | CP |
健康检查 | TCP/HTTP/MYSQL/Client Beat | 心跳 | TCP/HTTP/gRPC/Cmd | Keep Alive |
雪崩保护 | 有 | 有 | 无 | 无 |
访问协议 | HTTP/DNS | HTTP | HTTP/DNS | TCP |
SpringCloud集成 | 支持 | 支持 | 支持 | 支持 |
Zookeeper:CP设计,保证了一致性,集群搭建的时候,某个节点失效,则会进行选举行的leader,或者半数以上节点不可用,则无法提供服务,因此可用性没法满足
Eureka:AP原则,无主从节点,一个节点挂了,自动切换其他节点可以使用,去中心化
结论:
CAP 中的一致性和可用性进行一个权衡的结果,核心思想就是:我们无法做到强一致,但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性, 来自 ebay 的架构师提出
存在问题: 微服务拆分多个系统,服务之间相互依赖,可能由于 负载过高、流量突发或者网络异常等情况导致服务不可用
解决思路:
采用漏洞算法
服务之间调用出现异常,熔断停止调用,避免拖垮整个业务
保险丝,熔断服务,为了防止整个系统故障,包含当前和下游服务 下单服务 -》商品服务-》用户服务 -》(出现异常-》熔断风控服务
服务之间,空余资源降级给紧张资源使用
抛弃一些非核心对的接口和数据,返回兜底数据
限制服务的调用,将其隔离在外
熔断和降级互相交集
相同点:
不同点
官网地址:https://github.com/alibaba/Sentinel/wiki/%E6%8E%A7%E5%88%B6%E5%8F%B0
下载好jar包后的启动命令:
java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.0.jar
导入jar包:
com.alibaba.cloud spring-cloud-starter-alibaba-sentinel
配置文件:
# sentinel 限流 dashboard: 8080 控制台端口# port: 9998 本地启的端口,随机选个不能被占用的,与dashboard进行数据交互,会在应用对应的机器上启动一个 Http Server,该 Server 会与 Sentinel 控制台做交互, 若被占用,则开始+1一次扫描spring: cloud: sentinel: transport: dashboard: 127.0.0.1:8080 port: 9998
自定义 sentinel 异常返回结果:
@Componentpublic class MySentinelBlockHandler implements BlockExceptionHandler { /* FlowException //限流异常 DegradeException //降级异常 ParamFlowException //参数限流异常 SystemBlockException //系统负载异常 AuthorityException //授权异常 */ /** * V2.1.0 到 V2.2.0后,sentinel 里面依赖进行改动,且不向下兼容 * @param request * @param response * @param e * @throws Exception */ @Override public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception { System.out.println("request.getRequestURI() = " + request.getRequestURI()); System.out.println("request.getRemoteUser() = " + request.getRemoteUser()); System.out.println("request.getContextPath() = " + request.getContextPath()); System.out.println("request.getMethod() = " + request.getMethod()); // 降级业务 MapbackMap=new HashMap<>(); if (e instanceof FlowException){ backMap.put("code",-1); backMap.put("msg","限流-异常啦"); }else if (e instanceof DegradeException){ backMap.put("code",-2); backMap.put("msg","降级-异常啦"); }else if (e instanceof ParamFlowException){ backMap.put("code",-3); backMap.put("msg","热点-异常啦"); }else if (e instanceof SystemBlockException){ backMap.put("code",-4); backMap.put("msg","系统规则-异常啦"); }else if (e instanceof AuthorityException){ backMap.put("code",-5); backMap.put("msg","认证-异常啦"); } // 设置返回json数据 response.setStatus(200); response.setHeader("content-Type","application/json;charset=UTF-8"); response.getWriter().write(JSON.toJSONString(backMap)); }}
配置文件:
# 开启 feign 对 Sentinel 的支持feign: sentinel: enabled: true
简介:讲解JDK⼀些基础知识科普
简介:介绍什么是微服务的网关和应用场景
导入 jar 包:
org.springframework.cloud spring-cloud-starter-gateway
配置文件:
# 端口号server: port: 8888# application 的名字 不能有下划线 _spring: application: name: xdclass-api-gateway cloud:# nacos 注册与发现 nacos: discovery: server-addr: 127.0.0.1:8848# sentinel 限流 dashboard: 8080 控制台端口# port: 9999 本地启的端口,随机选个不能被占用的,与dashboard进行数据交互,会在应用对应的机器上启动一个 Http Server,该 Server 会与 Sentinel 控制台做交互, 若被占用,则开始+1一次扫描 sentinel: transport: dashboard: 127.0.0.1:8080 port: 9993 gateway: routes: #数组形式 - id: order-service #路由唯一标识 # uri: http://127.0.0.1:8000 #想要转发到的地址 uri: lb://xdclass-order-service # 从nacos 进行转发 lb 是负载均衡的意思 order: 1 #优先级,数字越小优先级越高 predicates: #断言过滤器 配置哪个路径才转发 【RoutePredicateFactory】 支持多个predicates请求转发,如果配置多个必须满足多个才能转发 - Path=/order-server/**# - Before=2020-10-30T18:59:00.000+08:00[Asia/Shanghai]# - Query=source # 这个时间【之前可以访问】2020-10-25T14:10:07.049+08:00[Asia/Shanghai] ZonedDateTime # Query=source 每个接口必须携带一个字段过来 source filters: #过滤器,请求在传递过程中通过过滤器修改 - StripPrefix=1 #去掉第一层前缀 discovery: locator: enabled: true # 开启网关拉取 nacos 服务#访问路径 http://localhost:8888/order-server/api/v1/video_order/list#转发路径 http://localhost:8000/api/v1/video_order/list#需要过滤器去掉前面第一层
自定义全局过滤:
//@Componentpublic class UserGlobalFilter implements GlobalFilter, Ordered { /** * 【网关不要加入太多业务逻辑,否则会影响性能】 */ @Override public Monofilter(ServerWebExchange exchange, GatewayFilterChain chain) { // 写业务逻辑 String token = exchange.getRequest().getHeaders().getFirst("Authorization"); // 根据业务鉴权 if (StrUtil.isEmpty(token)) { exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); Mono voidMono = exchange.getResponse().setComplete(); return voidMono; } Mono filter = chain.filter(exchange); return filter; } // 数字越小,优先级越高 @Override public int getOrder() { return 0; }}
1.快速排查问题所在
2.快速排查调用慢的情况
简介:讲解什么Sleuth链路追踪系统
[order-service,96f95a0dd81fe3ab,852ef4cfcdecabf3,false] 第一个值,spring.application.name的值 第二个值,96f95a0dd81fe3ab ,sleuth生成的一个ID,叫Trace ID,用来标识一条请求链路,一条请求链路中包含一个Trace ID,多个Span ID 第三个值,852ef4cfcdecabf3、spanid 基本的工作单元,获取元数据,如发送一个http 第四个值:false,是否要将该信息输出到zipkin服务中来收集和展示。
导入jar:
org.springframework.cloud spring-cloud-starter-sleuth
什么是zipkin
同类产品
StackDriver Trace (Google)
开始使用
java -jar zipkin-server-2.12.9-exec.jar
简介:使用Zipkin+Sleuth业务分析调用链路分析实战
org.springframework.cloud spring-cloud-starter-zipkin
spring: application: name: api-gateway zipkin: base-url: http://127.0.0.1:9411/ #zipkin地址 discovery-client-enabled: false #不用开启服务发现 sleuth: sampler: probability: 1.0 #采样百分比默认为0.1,即10%,这里配置1,是记录全部的sleuth信息,是为了收集到更多的数据(仅供测试用)。在分布式系统中,过于频繁的采样会影响系统性能,所以这里配置需要采用一个合适的值。
持久化的数据库sql脚本:
/*Navicat MySQL Data TransferSource Server : mydataSource Server Version : 50726Source Host : localhost:3306Source Database : zipkin_logTarget Server Type : MYSQLTarget Server Version : 50726File Encoding : 65001Date: 2020-10-26 17:17:01*/SET FOREIGN_KEY_CHECKS=0;-- ------------------------------ Table structure for zipkin_annotations-- ----------------------------DROP TABLE IF EXISTS `zipkin_annotations`;CREATE TABLE `zipkin_annotations` ( `trace_id_high` bigint(20) NOT NULL DEFAULT '0' COMMENT 'If non zero, this means the trace uses 128 bit traceIds instead of 64 bit', `trace_id` bigint(20) NOT NULL COMMENT 'coincides with zipkin_spans.trace_id', `span_id` bigint(20) NOT NULL COMMENT 'coincides with zipkin_spans.id', `a_key` varchar(255) NOT NULL COMMENT 'BinaryAnnotation.key or Annotation.value if type == -1', `a_value` blob COMMENT 'BinaryAnnotation.value(), which must be smaller than 64KB', `a_type` int(11) NOT NULL COMMENT 'BinaryAnnotation.type() or -1 if Annotation', `a_timestamp` bigint(20) DEFAULT NULL COMMENT 'Used to implement TTL; Annotation.timestamp or zipkin_spans.timestamp', `endpoint_ipv4` int(11) DEFAULT NULL COMMENT 'Null when Binary/Annotation.endpoint is null', `endpoint_ipv6` binary(16) DEFAULT NULL COMMENT 'Null when Binary/Annotation.endpoint is null, or no IPv6 address', `endpoint_port` smallint(6) DEFAULT NULL COMMENT 'Null when Binary/Annotation.endpoint is null', `endpoint_service_name` varchar(255) DEFAULT NULL COMMENT 'Null when Binary/Annotation.endpoint is null', UNIQUE KEY `trace_id_high` (`trace_id_high`,`trace_id`,`span_id`,`a_key`,`a_timestamp`) COMMENT 'Ignore insert on duplicate', KEY `trace_id_high_2` (`trace_id_high`,`trace_id`,`span_id`) COMMENT 'for joining with zipkin_spans', KEY `trace_id_high_3` (`trace_id_high`,`trace_id`) COMMENT 'for getTraces/ByIds', KEY `endpoint_service_name` (`endpoint_service_name`) COMMENT 'for getTraces and getServiceNames', KEY `a_type` (`a_type`) COMMENT 'for getTraces and autocomplete values', KEY `a_key` (`a_key`) COMMENT 'for getTraces and autocomplete values', KEY `trace_id` (`trace_id`,`span_id`,`a_key`) COMMENT 'for dependencies job') ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPRESSED;-- ------------------------------ Table structure for zipkin_dependencies-- ----------------------------DROP TABLE IF EXISTS `zipkin_dependencies`;CREATE TABLE `zipkin_dependencies` ( `day` date NOT NULL, `parent` varchar(255) NOT NULL, `child` varchar(255) NOT NULL, `call_count` bigint(20) DEFAULT NULL, `error_count` bigint(20) DEFAULT NULL, PRIMARY KEY (`day`,`parent`,`child`)) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPRESSED;-- ------------------------------ Table structure for zipkin_spans-- ----------------------------DROP TABLE IF EXISTS `zipkin_spans`;CREATE TABLE `zipkin_spans` ( `trace_id_high` bigint(20) NOT NULL DEFAULT '0' COMMENT 'If non zero, this means the trace uses 128 bit traceIds instead of 64 bit', `trace_id` bigint(20) NOT NULL, `id` bigint(20) NOT NULL, `name` varchar(255) NOT NULL, `remote_service_name` varchar(255) DEFAULT NULL, `parent_id` bigint(20) DEFAULT NULL, `debug` bit(1) DEFAULT NULL, `start_ts` bigint(20) DEFAULT NULL COMMENT 'Span.timestamp(): epoch micros used for endTs query and to implement TTL', `duration` bigint(20) DEFAULT NULL COMMENT 'Span.duration(): micros used for minDuration and maxDuration query', PRIMARY KEY (`trace_id_high`,`trace_id`,`id`), KEY `trace_id_high` (`trace_id_high`,`trace_id`) COMMENT 'for getTracesByIds', KEY `name` (`name`) COMMENT 'for getTraces and getSpanNames', KEY `remote_service_name` (`remote_service_name`) COMMENT 'for getTraces and getRemoteServiceNames', KEY `start_ts` (`start_ts`) COMMENT 'for getTraces ordering and range') ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPRESSED;
运行指令:
java -jar zipkin-server-2.12.9-exec.jar --STORAGE_TYPE=mysql --MYSQL_HOST=127.0.0.1 --MYSQL_TCP_PORT=3306 --MYSQL_DB=zipkin_log --MYSQL_USER=root --MYSQL_PASS=xdclass.net
新建一个 bootstrap.yml 文件
spring: application: name: xdclass-api-gateway cloud: nacos: config: server-addr: 127.0.0.1:8848 #Nacos配置中心地址 file-extension: yaml #文件拓展格式 profiles: active: dev
若不可用则重新编译项目:
重新构建下项目 mvn clean package -U然后重启IDEA
在nacos中配置:
${prefix}-${spring.profiles.active}.${file-extension}prefix 默认为 spring.application.name 的值spring.profiles.active 即为当前环境对应的 profile当 spring.profiles.active 为空时,对应的连接符 - 也将不存在,dataId 的拼接格式变成 ${prefix}.${file-extension}file-exetension 为配置内容的数据格式,可以通过配置项 spring.cloud.nacos.config.file-extension 来配置。目前只支持 properties 和 yaml 类型。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DiHWkiOC-1606960678014)(C:\Users\AOSSCI\AppData\Roaming\Typora\typora-user-images\image-20201026172918849.png)]
编写代码动态获取文件:
@RequestMapping("/api/v1/get")@RestController@RefreshScopepublic class GatewayConfigRefresh { @Value("${gateway.title}") private String gatewayTitle; @GetMapping("/test") public Object test(){ return gatewayTitle; }}
转载地址:http://bexxi.baihongyu.com/