微服务基础骨架搭建-03

微服务基础骨架搭建-03 这篇文章主要讲解Gateway网关搭建。网关是流量的入口主要负责路由转发、负载均衡、限流降级和统一鉴权。1. 路由转发、负载均衡1.1 依赖引入由于我们在父pom中引入了 spring-cloud-dependencies所有我们直接在gateway pom文件中引入两个依赖!--mfc-config-- dependency groupIdcom.mfar/groupId artifactIdmfc-config/artifactId /dependency !-- Source: https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-gateway -- dependency groupIdorg.springframework.cloud/groupId artifactIdspring-cloud-starter-gateway/artifactId /dependency !-- Source: https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-loadbalancer -- dependency groupIdorg.springframework.cloud/groupId artifactIdspring-cloud-starter-loadbalancer/artifactId /dependency1.2 配置server: port: 12000 spring: application: name: mfc-gateway config: import: optional:classpath:config.yml,optional:nacos:${spring.application.name}.properties cloud: gateway: server: webflux: routes: - id: user-service uri: lb://user-service predicates: - Path/user/** - id: auth-service uri: lb://auth-service predicates: - Path/auth/**1.3 测试启动mfc-user和mfc-gateway测试 mfc-user 服务上的接口 localhost:12020/user/hello把端口改为 12000localhost:12000/user/hello网关已成功转发但是我们访问原地址发现也成功了这是我们不希望看到的我们只希望通过网关访问而不是直接能访问到我们的服务接着往下看1.41.4 设置仅能通过网关访问服务思路在请求到网关后我们添加一个表示网关的请求头并设置值在服务端mfc-user配置是否开启仅能通过网关访问服务配置请求头和值如果开启并且请求头和值都通过则正常访问否则拒绝访问。1.4.1 gateway配置在配置文件添加spring: cloud: gateway: server: webflux: default-filters: - SetRequestHeaderX-MFC-Gateway-Token, ${mfc.gateway-only.secret} mfc: gateway-only: secret: ${MFC_GATEWAY_ONLY_SECRET:mfc-gateway-local-secret}请求头为 X-MFC-Gateway-Token值在下面配置的 mfc.gateway-only.secret 获取首先从环境变量 MFC_GATEWAY_ONLY_SECRET 获取没有配置环境变量就是用后面的默认值 mfc-gateway-local-secret1.4.2 添加请求头过滤器过滤器我们在 mfc-web 模块中添加实现代码的复用过滤器 GatewayOnlyFilterpackage com.mfar.mfc.web.filter; import jakarta.servlet.Filter; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; public class GatewayOnlyFilter implements Filter { private final String headerName; private final String secret; public GatewayOnlyFilter(String headerName, String secret) { this.headerName headerName; this.secret secret; } Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpRequest (HttpServletRequest) request; HttpServletResponse httpResponse (HttpServletResponse) response; String actualSecret httpRequest.getHeader(headerName); if (!constantTimeEquals(secret, actualSecret)) { httpResponse.setStatus(HttpServletResponse.SC_FORBIDDEN); httpResponse.getWriter().write(Forbidden: please access through gateway); return; } chain.doFilter(request, response); } private boolean constantTimeEquals(String expected, String actual) { if (actual null) { return false; } return MessageDigest.isEqual( expected.getBytes(StandardCharsets.UTF_8), actual.getBytes(StandardCharsets.UTF_8)); } }注册Configuration public class WebConfiguration { // 其他配置 /** * 网关请求头过滤器 * param enabled * param header * param secret * return */ Bean public FilterRegistrationBeanGatewayOnlyFilter gatewayOnlyFilter( Value(${mfc.gateway-only.enabled:false}) boolean enabled, Value(${mfc.gateway-only.header:X-MFC-Gateway-Token}) String header, Value(${mfc.gateway-only.secret:}) String secret) { FilterRegistrationBeanGatewayOnlyFilter registrationBean new FilterRegistrationBean(); registrationBean.setEnabled(enabled); if (!enabled) { registrationBean.setFilter(new GatewayOnlyFilter(header, )); return registrationBean; } if (!StringUtils.hasText(secret)) { throw new IllegalArgumentException(mfc.gateway-only.secret must not be blank when gateway-only is enabled); } registrationBean.setFilter(new GatewayOnlyFilter(header, secret)); registrationBean.addUrlPatterns(/*); registrationBean.setOrder(1); return registrationBean; } }1.4.3 服务端配置服务 mfc-user 依赖了 mfc-web并在配置文件添加mfc: gateway-only: enabled: true # 是否开启 业务服务只接受网关转发 header: X-MFC-Gateway-Token # 网关转发请求头 secret: ${MFC_GATEWAY_ONLY_SECRET:mfc-gateway-local-secret} # 网关转发密钥1.4.4 再次测试我们发现通过原地址已经无法访问了通过网关访问第一次成功第二次报错这是因为我们 nacos 服务列表 user-service实例有两个这也误打误撞地说明了我们的负载均衡起效了里有两个实例是因为有一个是我们mfc-user自己的有一个是Dubbo的当负载到Dubbo时就会出错因为Dubbo并没有这个接口因此为了避免服务间相互调用的Dubbo接口和提供给用户访问的接口之间冲突需要配置到不同的命名空间下看1.51.5 解决Dubbo接口与服务接口冲突可以看到mfc-rpc和mfc-config的默认命名空间我们之前都配置到同一个命名空间下d92dc5b6-1b48-465d-aa0c-ccd8e780c8af我们新增一个命名空间 mfc_dubbo把生成的命名空间 id 复制到 rpc.yml 中重启之后命名空间 mfc 就没有 dubbo 相关的实例了mfc_dubbo命名空间下通过网关访问就不会再报500错误了修改了dubbo再来测试一下登录功能是否正常登录是mfc-auth 通过dubbo调用mfc-user成功登录完美2. 限流降级使用sentinel2.1 依赖!--限流组件-- !--starter-- !-- Source: https://mvnrepository.com/artifact/com.alibaba.cloud/spring-cloud-starter-alibaba-sentinel -- dependency groupIdcom.alibaba.cloud/groupId artifactIdspring-cloud-starter-alibaba-sentinel/artifactId /dependency !--sentinel Spring Cloud Gateway 适配器 -- !-- Source: https://mvnrepository.com/artifact/com.alibaba.cloud/spring-cloud-alibaba-sentinel-gateway -- dependency groupIdcom.alibaba.cloud/groupId artifactIdspring-cloud-alibaba-sentinel-gateway/artifactId /dependency !--nacos存储规则-- !-- Source: https://mvnrepository.com/artifact/com.alibaba.csp/sentinel-datasource-nacos -- dependency groupIdcom.alibaba.csp/groupId artifactIdsentinel-datasource-nacos/artifactId /dependency !--用于连接 Dashboard-- !-- Source: https://mvnrepository.com/artifact/com.alibaba.csp/sentinel-transport-simple-http -- dependency groupIdcom.alibaba.csp/groupId artifactIdsentinel-transport-simple-http/artifactId /dependency2.2 配置配置sentinel dashboard地址在网关配置文件添加spring: cloud: sentinel: transport: dashboard: 192.168.23.129:8080 # Sentinel Dashboard 地址2.3 测试我们启动 mfc-gateway 和 mfc-user 服务再访问 localhost:12000/user/hello就可以在dashboard上新接入的应用限流配置点击“流控”增加规则设置QPS为2点击新增添加成功接下来我们快速刷新更改测试的接口可以看到被限流了对于各种流控规则和降级这里就不展开说了有兴趣的可以自己搜搜看2.4 sentinel规则存储策略sentinel规则默认存储在内存中的我们重启sentinel并再次访问发现规则不存在了那就太糟糕了再重启两个服务狂刷接口也不限流了限流彻底失效这里我们选择 nacos 持久化规则。首先明确一下我们刚刚的规则是存在sentinel dashboard内存中的修改后直接对业务生效但是重启后规则丢失使用nacos持久化规则有两种做法1. 只做 nacos 持久化不做双向同步。在nacos配置规则规则只是单向的从nacos——sentinel2. 双向同步。在sentinel dashboard修改的规则会持久化到nacos在nacos修改的规则也会在sentinel展示我们这里实现第一种第二种要修改sentinel源码参考下面这篇文章手把手教你改造 Sentinel Dashboard 实现配置持久化 - 听到微笑 - 博客园2.4.1 修改网关配置文件rule-type: GW_FLOW 表示网关的流控点进去还有很多spring: cloud: sentinel: transport: dashboard: 192.168.23.129:8858 # Sentinel Dashboard 地址 datasource: flow: nacos: server-addr: 192.168.23.129:8848 username: nacos password: nacos123 namespace: 53a421a8-358a-42f1-a4cc-342903e415ac />2.4.2 配置nacos创建 mfc_sentinel 命名空间将命名空间 id 填到上面配置文件的namespace切换到配置中心 mfc_sentinel 命名空间下创建配置Data ID、Group要和上面配置文件的保持一致配置格式选JSON默认是JSON所以上面配置文件没有配注意这里是使用的是 rule-type: GW_FLOW 网关限流所以发布后不会马上有这条流控规则要访问一次后才会有快速刷新成功限流规则配置参考一面这篇文章Nacos配置Sentinel规则JSON说明 - 邢帅杰 - 博客园2.5 nacos sentinel流控规则配置2.5.1 配置文件在上面 2.4.1 配置文件那里 spring.cloud.sentinel.datasource 下面的 flow其实可以是任意值只是一个数据源的名称可以任意取名保证唯一即可flow下面的 nacos 才是数据源类型。我们修改一下spring: cloud: sentinel: transport: dashboard: 192.168.23.129:8858 # Sentinel Dashboard 地址 datasource: gw-flow: nacos: server-addr: 192.168.23.129:8848 username: nacos password: nacos123 namespace: 53a421a8-358a-42f1-a4cc-342903e415ac >rule-type是GW_API_GROUP这个叫做 api 分组可以理解为给每个接口取一个别名如 /user/hello 可以取 user_hello_api名字随意取保证唯一即可。在 mfc-gateway-flow-rules 把 resource 指定为user_hello_api这样就精准到接口了再在 mfc-gateway-flow-rules 给这个接口配置具体的规则。听得是不是有点懵接下来实操一下往下看2.5.2 nacos配置创建 mfc-gateway-api-groups[ { apiName: user_hello_api, predicateItems: [ { pattern: /user/hello, matchStrategy: 0 } ] }, { apiName: user_test_api, predicateItems: [ { pattern: /user/test, matchStrategy: 0 } ] } ]apiName 自定义 API 名pattern 要匹配的路径matchStrategy 匹配方式 0 精确匹配 1 前缀匹配 2 正则匹配看到这里是不是有点眉目了mfc-gateway-api-groups 就是配置接口路径的取一个唯一的自定义 API 名编辑 mfc-gateway-flow-rule[ { resource: user_hello_api, resourceMode: 1, grade: 1, count: 1, intervalSec: 1, controlBehavior: 0, burst: 0 }, { resource: user_test_api, resourceMode: 1, grade: 1, count: 2, intervalSec: 1, controlBehavior: 0, burst: 0 } ]resource: user_hello_api之前填的是服务名是对整个服务起效的现在是对单个接口起效精准resourceMode: 1resourceMode: 0 按 gateway 路由 ID 限流比如 user-serviceresourceMode: 1 按自定义 API 分组限流比如 user_hello_api现在配置中心mfc_sentinel命名空间下2.5.3 测试先发布 mfc-gateway-api-groups再发布 mfc-gateway-flow-rules重启 mfc-gateway访问一次 http://localhost:12000/user/hello 或者 http://localhost:12000/user/test去 Sentinel Dashboard 看 mfc-gateway完美加载优雅狂刷限流起效重启 sentinel 和 gateway刷新dashboar 规则没有加载访问一次后就有了说明持久化成功了只重启 sentinel 刷新页面规则还在所以我两个都重启了2.5.4 总结这个方法只是在 nacos 上配置的规则能持久化并被加载在 sentinel dashboard 配置的规则虽然也能起效当不能持久化到 nacos 上重启后就失效。上面配的spring.cloud.sentinel.datasource下只有两个gw-flow、gw-api-group如果所有服务都配在这里那岂不是很杂乱因此可以这样spring: cloud: sentinel: transport: dashboard: 192.168.23.129:8858 # Sentinel Dashboard 地址 datasource: user-flow: nacos: >3. 统一鉴权今天先写这么多明天再写