我前几年一直在做安卓,后来接触过小程序,最近几年开始接触spring boot,安卓和小程序比较技术比较专一,后台涉及面就很广了,最近做的项目多是CRUD,感觉比较单调,就开始学习微服务,主要学习资料是《谷粒商城》,之前也看过,当时也是脑子一热,有个印象,现在开发一年多后台以后,再看的话,稍微有点感觉,就把重点记录一下,不涉及具体编码,源码和课件可以在这个地方下载谷粒商城课件源码.rar,不过官方下载的代码不怎么全,哔站有人自己整理了一份,可以参考一下
Gulimall高级篇_持续更新中
先上一张谷粒商城-微服务架构图
微服务划分图
微服务架构风格, 就像是把一个单独的应用程序开发为一套小服务, 每个小服务运行在自己的进程中, 并使用轻量级机制通信, 通常是 HTTP API。 这些服务围绕业务能力来构建,并通过完全自动化部署机制来独立部署。 这些服务使用不同的编程语言书写, 以及不同数据存储技术, 并保持最低限度的集中式管理。
简而言之: 拒绝大型单体应用, 基于业务边界进行服务微化拆分, 各个服务独立部署运行。
集群是个物理形态, 分布式是个工作方式。只要是一堆机器, 就可以叫集群, 他们是不是一起协作着干活, 这个谁也不知道;“分布式系统是若干独立计算机的集合, 这些计算机对于用户来说就像单个相关系统”。分布式系统(distributed system) 是建立在网络之上的软件系统。分布式是指将不同的业务分布在不同的地方。集群指的是将几台服务器集中在一起, 实现同一业务。分布式中的每一个节点, 都可以做集群。 而集群并不一定就是分布式的。节点: 集群中的一个服务器
3、远程调用在分布式系统中, 各个服务可能处于不同主机, 但是服务之间不可避免的需要互相调用, 我
们称为远程调用。SpringCloud 中使用 HTTP+JSON 的方式完成远程调用
分布式系统中, A 服务需要调用 B 服务, B 服务在多台机器中都存在, A 调用任意一个服务器均可完成功能。为了使每一个服务器都不要太忙或者太闲, 我们可以负载均衡的调用每一个服务器, 提升网站的健壮性。
常见的负载均衡算法:
轮询: 为第一个请求选择健康池中的第一个后端服务器, 然后按顺序往后依次选择, 直到最后一个, 然后循环。
最小连接: 优先选择连接数最少, 也就是压力最小的后端服务器, 在会话较长的情况下可以考虑采取这种方式。
散列: 根据请求源的 IP 的散列(hash) 来选择要转发的服务器。 这种方式可以一定程度上保证特定用户能连接到相同的服务器。 如果你的应用需要处理状态而要求用户能连接到和之前相同的服务器, 可以考虑采取这种方式。
A 服务调用 B 服务, A 服务并不知道 B 服务当前在哪几台服务器有, 哪些正常的, 哪些服务已经下线。 解决这个问题可以引入注册中心;
如果某些服务下线, 我们其他人可以实时的感知到其他服务的状态, 从而避免调用不可用的服务
每一个服务最终都有大量的配置, 并且每个服务都可能部署在多台机器上。 我们经常需要变更配置, 我们可以让每个服务在配置中心获取自己的配置。配置中心用来集中管理微服务的配置信息
在微服务架构中, 微服务之间通过网络进行通信, 存在相互依赖, 当其中一个服务不可用时,有可能会造成雪崩效应。 要防止这样的情况, 必须要有容错机制来保护服务。
1) 、 服务熔断
a. 设置服务的超时, 当被调用的服务经常失败到达某个阈值, 我们可以开启断路保护机制, 后来的请求不再去调用这个服务。 本地直接返回默认的数据
2) 、 服务降级
a. 在运维期间, 当系统处于高峰期, 系统资源紧张, 我们可以让非核心业务降级运行。 降级: 某些服务不处理, 或者简单处理【抛异常、 返回 NULL、调用 Mock 数据、 调用 Fallback 处理逻辑】 。
在微服务架构中, API Gateway 作为整体架构的重要组件, 它抽象了微服务中都需要的公共功能, 同时提供了客户端负载均衡, 服务自动熔断, 灰度发布, 统一认证, 限流流控, 日志统计等丰富的功能, 帮助我们解决很多 API 管理难题
项目结构如下
创建一个spring boot的project,gulimall,然后建立各个module,选择maven,然后project聚合module
gulimall的pom.xml配置
4.0.0 com.atguigu.gulimall >gulimall0.0.1-SNAPSHOT gulimall 聚合服务 pom gulimall-coupon gulimall-member gulimall-order gulimall-product gulimall-ware renren-fast renren-generator gulimall-common
gulimall-common是基础,其他Module引用这个,gulimall-common的pom.xml文件
>gulimall com.atguigu.gulimall 0.0.1-SNAPSHOT 4.0.0 >gulimall-common每一个微服务公共的依赖,bean,工具类等
其他module的pom.xml公共部分
技术搭配方案4.0.0 org.springframework.boot >spring-boot-starter-parent2.1.8.RELEASE com.atguigu.gulimall >gulimall-order0.0.1-SNAPSHOT gulimall-order 各模块描述 1.8 Greenwich.SR3 .... com.atguigu.gulimall >gulimall-common0.0.1-SNAPSHOT
在gulimall-common引入SpringCloud Alibaba的依赖
Nacos>gulimall com.atguigu.gulimall 0.0.1-SNAPSHOT 4.0.0 >gulimall-common每一个微服务公共的依赖,bean,工具类等 com.baomidou >mybatis-plus-boot-starter3.2.0 org.projectlombok >lombok1.18.8 org.apache.httpcomponents >httpcore4.4.12 commons-lang >commons-lang2.6 mysql >mysql-connector-java8.0.17 javax.servlet >servlet-api2.5 provided com.alibaba.cloud >spring-cloud-starter-alibaba-nacos-discoverycom.alibaba.cloud >spring-cloud-starter-alibaba-nacos-configjavax.validation >validation-api2.0.1.Final com.alibaba.cloud >spring-cloud-alibaba-dependencies2.1.0.RELEASE pom import
nacos文档
我电脑win7,安装linux失败,只能用windows环境了,下载nacos-server,我这边是nacos-server-2.0.3,然后点击bin目录下的startup.cmd,如果失败的话,可能是因为集群问题,右击startup.cmd,修改里边的
rem set MODE="cluster" set MODE="standalone"
或者通过启动命令(standalone代表着单机模式运行,非集群模式):
startup.cmd -m standalone
启动起来是这个样子
端口8848,通过 http://localhost:8848/nacos/访问,用户名和密码都是nacos,登录进去
在common模块的pom.xml中引用nacos
服务注册/发现
com.alibaba.cloud >spring-cloud-starter-alibaba-nacos-discovery
配置中心来做配置管理
com.alibaba.cloud >spring-cloud-starter-alibaba-nacos-config
在需要使用nacos的模块里进行配置,在src/main/resources下新建文件bootstrap.properties
spring.application.name=gulimall-coupon spring.cloud.nacos.config.server-addr=127.0.0.1:8848 #配置命名空间 spring.cloud.nacos.config.namespace=6f130311-ca32-459c-8a93-f225d0fa5d43 #配置分组 spring.cloud.nacos.config.group=prod #nacos上添加文件,从nacos上读取配置,本地的可以注释掉 spring.cloud.nacos.config.ext-config[0].data-id=datasource.yml spring.cloud.nacos.config.ext-config[0].group=dev spring.cloud.nacos.config.ext-config[0].refresh=true spring.cloud.nacos.config.ext-config[1].data-id=mybatis.yml spring.cloud.nacos.config.ext-config[1].group=dev spring.cloud.nacos.config.ext-config[1].refresh=true spring.cloud.nacos.config.ext-config[2].data-id=other.yml spring.cloud.nacos.config.ext-config[2].group=dev spring.cloud.nacos.config.ext-config[2].refresh=true
这是所有配置,在测试的时候,可以一点点添加配置,同时nacos网页上进行相应配置,才能看出效果
我把我nacos网页上的配置贴一下
配置列表
datasource.yml,mybatis.yml和other.yml对应配置里的
#nacos上添加文件,从nacos上读取配置,本地的可以注释掉 spring.cloud.nacos.config.ext-config[0].data-id=datasource.yml spring.cloud.nacos.config.ext-config[0].group=dev spring.cloud.nacos.config.ext-config[0].refresh=true spring.cloud.nacos.config.ext-config[1].data-id=mybatis.yml spring.cloud.nacos.config.ext-config[1].group=dev spring.cloud.nacos.config.ext-config[1].refresh=true spring.cloud.nacos.config.ext-config[2].data-id=other.yml spring.cloud.nacos.config.ext-config[2].group=dev spring.cloud.nacos.config.ext-config[2].refresh=true
内容分别是
datasource.yml:
spring:
datasource:
username: root
password: root
url: jdbc:mysql://localhost:3306/gulimall_sms
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis.yml
mybatis-plus:
mapper-locations: classpath:/mapper/**
@RefreshScope
@EnableDiscoveryClient
@SpringBootApplication
@MapperScan("com.atguigu.gulimall.coupon.dao")
public class GulimallCouponApplication {
public static void main(String[] args) {
SpringApplication.run(GulimallCouponApplication.class, args);
}
}
feign 远程调用
Feign 是一个声明式的 HTTP 客户端, 它的目的就是让远程调用更加简单。 Feign 提供了 HTTP请求的模板, 通过编写简单的接口和插入注解, 就可以定义好 HTTP 请求的参数、 格式、 地址等信息。
Feign 整合了 Ribbon(负载均衡) 和 Hystrix(服务熔断), 可以让我们不再需要显式地使用这两个组件。
根据以上配置,配置多个服务,然后启动,服务之间进行交互,需要使用到feign,引入open-feign,在gulimall-member模块调用gulimall-coupon中的服务,在gulimall-member的pom.xml引入open-feign,
org.springframework.cloud >spring-cloud-starter-openfeign
在GulimallMemberApplication文件上,加入注解,EnableFeignClients
@EnableFeignClients(basePackages = "com.atguigu.gulimall.member.feign")
@EnableDiscoveryClient
@SpringBootApplication
@MapperScan("com.atguigu.gulimall.member.dao")
public class GulimallMemberApplication {
public static void main(String[] args) {
SpringApplication.run(GulimallMemberApplication.class, args);
}
}
新建feign包,新建CouponFeignService接口
@FeignClient("gulimall-coupon")
public interface CouponFeignService {
@RequestMapping("/coupon/coupon/member/list")
public R membercoupons();
}
返回类型和名称与需要调用的方法保持一致。
这个对应的是CouponController里的内容
@RequestMapping("/member/list")
public R membercoupons(){
CouponEntity couponEntity = new CouponEntity();
couponEntity.setCouponName("满100减10");
return R.ok().put("coupons",Arrays.asList(couponEntity));
}
网页访问http://localhost:8000/member/member/coupons,返回
{"msg":"success","code":0,"coupons":[{"id":null,"couponType":null,"couponImg":null,"couponName":"满100减10","num":null,"amount":null,"perLimit":null,"minPoint":null,"startTime":null,"endTime":null,"useType":null,"note":null,"publishCount":null,"useCount":null,"receiveCount":null,"enableStartTime":null,"enableEndTime":null,"code":null,"memberLevel":null,"publish":null}],"member":{"id":null,"levelId":null,"username":null,"password":null,"nickname":"张三","mobile":null,"email":null,"header":null,"gender":null,"birth":null,"city":null,"job":null,"sign":null,"sourceType":null,"integration":null,"growth":null,"status":null,"createTime":null}}
再比如
@FeignClient("gulimall-coupon")
public interface CouponFeignService {
@PostMapping("/coupon/spubounds/save")
R saveSpuBounds(@RequestBody SpuBoundTo spuBoundTo);
@PostMapping("/coupon/skufullreduction/saveinfo")
R saveSkuReduction(@RequestBody SkuReductionTo skuReductionTo);
}
对应
@RestController
@RequestMapping("coupon/spubounds")
public class SpuBoundsController {
@PostMapping("/save")
//@RequiresPermissions("coupon:spubounds:save")
public R save(@RequestBody SpuBoundsEntity spuBounds){
spuBoundsService.save(spuBounds);
return R.ok();
}
@RestController
@RequestMapping("coupon/skufullreduction")
public class SkuFullReductionController {
@PostMapping("/saveinfo")
public R saveInfo(@RequestBody SkuReductionTo reductionTo){
skuFullReductionService.saveSkuReduction(reductionTo);
return R.ok();
}
跨域问题
使用vue调用后台接口,有可能会出现跨域问题,解决办法有两种,一个简单的就是在controller上添加@CrossOrigin(origins = “*”),另一种就是使用网关,gateway
gatewaySpring Cloud Gateway 旨在提供一种简单而有效的方式来对 API 进行路由, 并为他们提供切面, 例如: 安全性, 监控/指标 和弹性等。
官方文档地址:https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.1.3.RELEASE/single/spring-cloud-gateway.html
为什么使用 API 网关?
API 网关出现的原因是微服务架构的出现, 不同的微服务一般会有不同的网络地址, 而外部客户端可能需要调用多个服务的接口才能完成一个业务需求, 如果让客户端直接与各个微服务通信, 会有以下的问题:
1 客户端会多次请求不同的微服务, 增加了客户端的复杂性。
2 存在跨域请求, 在一定场景下处理相对复杂。
3认证复杂, 每个服务都需要独立认证。
4难以重构, 随着项目的迭代, 可能需要重新划分微服务。 例如, 可能将多个服务合并成一个或者将一个服务拆分成多个。 如果客户端直接与微服务通信, 那么重构将会很难实施。
5 某些微服务可能使用了防火墙 / 浏览器不友好的协议, 直接访问会有一定的困难。
以上这些问题可以借助 API 网关解决。 API 网关是介于客户端和服务器端之间的中间层,所有的外部请求都会先经过 API 网关这一层。 也就是说, API 的实现方面更多的考虑业务逻辑, 而安全、 性能、 监控可以交由 API 网关来做, 这样既提高业务灵活性又不缺安全性:使用 API 网关后的优点如下:1 易于监控。 可以在网关收集监控数据并将其推送到外部系统进行分析。2 易于认证。 可以在网关上进行认证, 然后再将请求转发到后端的微服务, 而无须在每个微服务中进行认证。3 减少了客户端与各个微服务之间的交互次数。
客户端发送请求给网关, 网关 HandlerMapping 判断是否请求满足某个路由, 满足就发给网关的 WebHandler。 这个 WebHandler 将请求交给一个过滤器链, 请求到达目标服务之前, 会执行所有过滤器的 pre 方法。 请求到达目标服务处理之后再依次执行所有过滤器的 post 方法。
一句话: 满足某些断言(predicates) 就路由到指定的地址(uri) , 使用指定的过滤器(filter)
新建gulimall-gateway模块,pom.xml引入gateWay,同样也要注册到nacos中
org.springframework.cloud >spring-cloud-starter-gateway
项目结构
application.properties
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848 spring.application.name=gulimall-gateway server.port=88
application.yml
spring:
cloud:
gateway:
routes:
# - id: test_route
# uri: https://www.baidu.com
# predicates:
# - Query=url,baidu
#
# - id: qq_route
# uri: https://www.qq.com
# predicates:
# - Query=url,qq
- id: product_route
uri: lb://gulimall-product
predicates:
- Path=/api/product/**
filters:
- RewritePath=/api/(?.*),/${segment}
- id: third_party_route
uri: lb://gulimall-third-party
predicates:
- Path=/api/thirdparty/**
filters:
- RewritePath=/api/thirdparty/(?.*),/${segment}
- id: member_route
uri: lb://gulimall-member
predicates:
- Path=/api/member/**
filters:
- RewritePath=/api/(?.*),/${segment}
- id: ware_route
uri: lb://gulimall-ware
predicates:
- Path=/api/ware/**
filters:
- RewritePath=/api/(?.*),/${segment}
- id: admin_route
uri: lb://renren-fast
predicates:
- Path=/api/**
filters:
- RewritePath=/api/(?.*),/renren-fast/${segment}
## 前端项目,/api
## http://localhost:88/api/captcha.jpg http://localhost:8080/renren-fast/captcha.jpg
## http://localhost:88/api/product/category/list/tree http://localhost:10000/product/category/list/tree
bootstrap.properties
spring.application.name=gulimall-gateway spring.cloud.nacos.config.server-addr=127.0.0.1:8848 spring.cloud.nacos.config.namespace=4065e888-a2bc-4c10-a1dd-8a84773f7e2e
这样,前端只需要访问http://localhost:88/api,网关就可以根据Path跳转到不同的模块
@EnableDiscoveryClient
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class GulimallGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GulimallGatewayApplication.class, args);
}
}
解决跨域问题
src/maiin下新建config文件夹,新建GulimallCorsConfiguration
@Configuration
public class GulimallCorsConfiguration {
@Bean
public CorsWebFilter corsWebFilter(){
UrlbasedCorsConfigurationSource source = new UrlbasedCorsConfigurationSource();
CorsConfiguration corsConfiguration = new CorsConfiguration();
//1、配置跨域
corsConfiguration.addAllowedHeader("*");
corsConfiguration.addAllowedMethod("*");
corsConfiguration.addAllowedOrigin("*");
corsConfiguration.setAllowCredentials(true);
source.registerCorsConfiguration("/**",corsConfiguration);
return new CorsWebFilter(source);
}
}



