程序员的资源宝库

网站首页 > gitee 正文

谷粒商城学习笔记(3)-- 分布式组件

sanyeah 2024-04-13 16:14:35 gitee 5 ℃ 0 评论

微服务-注册中心、配置中心、网关

在这里插入图片描述

一、SpringCloud Alibaba简介

SpringCloud个人笔记(尚硅谷):https://blog.csdn.net/exodus3/category_10661527.html

阿里18年开发的微服务一站式解决方案。https://github.com/alibaba/spring-cloud-alibaba/blob/master/README-zh.md

  • 注册中心:
  • 配置中心:
  • 网关:

netflix把feign闭源了,spring cloud开了个open feign

在common的pom.xml中加入

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-dependencies</artifactId>
            <version>2.2.0.RELEASE</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

上面是依赖管理,相当于以后在dependencies里引spring cloud alibaba就不用写版本号, 全用dependencyManagement进行管理。注意它和普通依赖的区别,它只是备注一下,并没有加入依赖

二、Nacos

一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。

nacos作为我们服务的注册中心和配置中心。

注册中心文档:https://github.com/alibaba/spring-cloud-alibaba/tree/master/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example

安装启动nacos:下载–解压–双击bin/startup.cmd。http://127.0.0.1:8848/nacos/ (端口默认8848)
账号密码nacos/nacos

使用nacos:

  • 在某个项目里properties里写spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848(yaml同理,指定nacos的地址)。再指定applicatin.name告诉注册到nacos中以什么命名

  • 依赖:放到common里,不写版本是因为里面有了版本管理

<dependency>
   <groupId>com.alibaba.cloud</groupId>
   <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
  • 使用 @EnableDiscoveryClient 注解开启服务注册与发现功能
@SpringBootApplication
@EnableDiscoveryClient
public class ProviderApplication {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @RestController
    class EchoController {
        @GetMapping(value = "/echo/{string}")
        public String echo(@PathVariable String string) {
            return string;
        }
    }
}

最后application.yml内容,配置了服务中心名和当前模块名字

spring:
      datasource:
        username: root
        password: root
        url: jdbc:mysql://192.168.56.10:3306/gulimall-sms?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
        driver-class-name: com.mysql.jdbc.Driver
      cloud:
        nacos:
          discovery:
            server-addr: 127.0.0.1:8848
      application:
        name: gulimall-coupon
    
    
    mybatis-plus:
      mapper-locations: classpath:/mapper/**/*.xml
      global-config:
        db-config:
          id-type: auto
          logic-delete-value: 1
          logic-not-delete-value: 0
    
    server:
      port: 7000

然后依次给member项目、配置上面的yaml,改下name就行。再给每个项目配置类上加上注解@EnableDiscoveryClient

nacos测试:

测试member和coupon的远程调用

想要获取当前会员领取到的所有优惠券。先去注册中心找优惠券服务,注册中心调一台优惠券服务器给会员,会员服务器发送请求给这台优惠券服务器,然后对方响应。

  • 服务请求方发送了2次请求,先问nacos要地址,然后再请求

三、Feign(远程调用)与注册中心

声明式远程调用

feign是一个声明式的HTTP客户端,目的就是让远程调用更加简单。给远程服务发的是HTTP请求。

会员服务想要远程调用优惠券服务,只需要给会员服务里引入openfeign依赖,它就有了远程调用其它服务的能力。

pom.xml

<dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

我们之前在member的pom.xml已经引用过了(微服务)。

在coupon中修改如下的内容

@RequestMapping("coupon/coupon")
public class CouponController {
    @Autowired
    private CouponService couponService;

    @RequestMapping("/member/list")
    public R membercoupons(){    //全系统的所有返回都返回R
        // 应该去数据库查用户对于的优惠券,但这个我们简化了,不去数据库查了,构造了一个优惠券给它返回
        CouponEntity couponEntity = new CouponEntity();
        couponEntity.setCouponName("满100-10");//优惠券的名字
        return R.ok().put("coupons",Arrays.asList(couponEntity));
    }

这样我们准备好了优惠券的调用内容

在member的配置类上加注解@EnableFeignClients,告诉member是一个远程调用客户端,member要调用东西的

/*
 * 想要远程调用的步骤:
 * 1 引入openfeign
 * 2 编写一个接口,接口告诉springcloud这个接口需要调用远程服务
 * 	2.1 在接口里声明@FeignClient("gulimall-coupon")它是一个远程调用客户端且要调用coupon服务
 * 	2.2 要调用coupon服务的/coupon/coupon/member/list方法
 * 3 开启远程调用功能 @EnableFeignClients,要指定远程调用功能放的基础包
 * */
 @EnableFeignClients(basePackages="com.atguigu.gulimall.member.feign")//扫描接口方法注解
 @EnableDiscoveryClient
 @SpringBootApplication
 public class gulimallMemberApplication {
 
 	public static void main(String[] args) {
 		SpringApplication.run(gulimallMemberApplication.class, args);
 	}
 }

那么要调用什么东西呢?就是我们刚才写的优惠券的功能,复制方法部分,在member的com.atguigu.gulimall.member.feign包下新建类:

@FeignClient("gulimall-coupon") //告诉spring cloud这个接口是一个远程客户端,要调用coupon服务(nacos中找到),具体是调用coupon服务的/coupon/coupon/member/list对应的方法
public interface CouponFeignService {
    // 远程服务的url
    @RequestMapping("/coupon/coupon/member/list")//注意写全优惠券类上还有映射//注意我们这个地方不是控制层,所以这个请求映射请求的不是我们服务器上的东西,而是nacos注册中心的
    public R membercoupons();//得到一个R对象
}

@FeignClient+@RequestMapping构成远程调用的坐标

其它类中看似只是调用了CouponFeignService.membercoupons(),而实际上该方法跑去nacos里和rpc里调用了才拿到东西返回

然后我们在member的控制层写一个测试请求

@RestController
@RequestMapping("member/member")
public class MemberController {
    @Autowired
    private MemberService memberService;

    @Autowired
    CouponFeignService couponFeignService;

    @RequestMapping("/coupons")
    public R test(){
        MemberEntity memberEntity = new MemberEntity();
        memberEntity.setNickname("会员昵称张三");
        R membercoupons = couponFeignService.membercoupons();//假设张三去数据库查了后返回了张三的优惠券信息

        //打印会员和优惠券信息
        return R.ok().put("member",memberEntity).put("coupons",membercoupons.get("coupons"));
    }
}

重新启动服务

输入 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}
}

coupon里的R.ok()是什么,就是设置了个msg

public class R extends HashMap<String, Object> {//R继承了HashMap
   // ok是个静态方法,new了一个R对象,并且
   public static R ok(String msg) {
       R r = new R();
       r.put("msg", msg);//调用了super.put(key, value);,即hashmap的put
       return r;
   }
}

四、Nacos作为配置中心

我们还可以用nacos作为配置中心。配置中心的意思是不在application.properties等文件中配置了,而是放到nacos配置中心公用,这样无需每台机器都改。

官方教程:https://github.com/alibaba/spring-cloud-alibaba/blob/master/spring-cloud-alibaba-examples/nacos-example/nacos-config-example/readme-zh.md

common中添加依赖 nacos配置中心

<dependency>
   <groupId>com.alibaba.cloud</groupId>
   <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

在coupons项目中创建/src/main/resources/bootstrap.properties ,这个文件是springboot里规定的,它优先级别比application.properties高

 # 改名字,对应nacos里的配置文件名
 spring.application.name=gulimall-coupon
 spring.cloud.nacos.config.server-addr=127.0.0.1:8848

还是原来我们使用配置的方式,只不过优先级变了,所以匹配到了nacos的配置

还是配合@Value注解使用

@RestController
@RequestMapping("coupon/coupon")
public class CouponController {
    @Autowired
    private CouponService couponService;

    @Value("${coupon.user.name}")//从application.properties中获取//不要写user.name,它是环境里的变量
    private String name;
    @Value("${coupon.user.age}")
    private Integer age;
    @RequestMapping("/test")
    public R test(){

        return R.ok().put("name",name).put("age",age);
    }
}    

浏览器去nacos里的配置列表,点击+号,data ID:gulimall-coupon.properties,配置

# gulimall-coupon.properties
coupon.user.name="配置中心"      
coupon.user.age=12

然后点击发布。重启coupon(生产中加入@RefreshScope即可),
输入 http://localhost:7000/coupon/coupon/test

{"msg":"success","code":0,"name":"配置中心","age":12}

但是修改肿么办?实际生产中不能重启应用。在coupon的控制层上加@RefreshScope

@RefreshScope
@RestController
@RequestMapping("coupon/coupon")
public class CouponController {
    @Autowired
    private CouponService couponService;

    @Value("${coupon.user.name}")//从application.properties中获取//不要写user.name,它是环境里的变量
    private String name;
    @Value("${coupon.user.age}")
    private Integer age;
    @RequestMapping("/test")
    public R test(){

        return R.ok().put("name",name).put("age",age);
    }

重启后(让注解生效),在nacos浏览器里修改配置,修改就可以观察到能动态修改了

nacos的配置内容优先于项目本地的配置内容。

配置中心进阶

在nacos浏览器中还可以配置:

  • 命名空间:用作配置隔离。(一般每个微服务一个命名空间)

    • 默认public。默认新增的配置都在public空间下

    • 开发、测试、开发可以用命名空间分割。properties每个空间有一份。

    • 在bootstrap.properties里配置(测试完去掉,学习不需要)

      # 可以选择对应的命名空间 # 写上对应环境的命名空间ID
      spring.cloud.nacos.config.namespace=b176a68a-6800-4648-833b-be10be8bab00
      
    • 也可以为每个微服务配置一个命名空间,微服务互相隔离

  • 配置集:一组相关或不相关配置项的集合。

  • 配置集ID:类似于配置文件名,即Data ID

  • 配置分组:默认所有的配置集都属于DEFAULT_GROUP。双十一,618的优惠策略改分组即可

    # 更改配置分组
    spring.cloud.nacos.config.group=DEFAULT_GROUP
    

最终方案:每个微服务创建自己的命名空间,然后使用配置分组区分环境(dev/test/prod)

加载多配置集

我们要把原来application.yml里的内容都分文件抽离出去。我们在nacos里创建好后,在coupons里指定要导入的配置即可。

bootstrap.properties

在其中用数组spring.cloud.nacos.config.extension-configs[]写明每个配置集

spring.application.name=gulimall-coupon
    
spring.cloud.nacos.config.server-addr=127.0.0.1:8848
# 可以选择对应的命名空间 # 写上对应环境的命名空间ID
spring.cloud.nacos.config.namespace=b176a68a-6800-4648-833b-be10be8bab00
# 更改配置分组
spring.cloud.nacos.config.group=dev

#新版本不建议用下面的了
#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

spring.cloud.nacos.config.extension-configs[0].data-id=datasource.yml
spring.cloud.nacos.config.extension-configs[0].group=dev
spring.cloud.nacos.config.extension-configs[0].refresh=true

spring.cloud.nacos.config.extension-configs[1].data-id=mybatis.yml
spring.cloud.nacos.config.extension-configs[1].group=dev
spring.cloud.nacos.config.extension-configs[1].refresh=true

spring.cloud.nacos.config.extension-configs[2].data-id=other.yml
spring.cloud.nacos.config.extension-configs[2].group=dev
spring.cloud.nacos.config.extension-configs[2].refresh=true

控制台输出信息有:

2020-06-25 00:04:13.677  WARN 17936 --- [           main] c.a.c.n.c.NacosPropertySourceBuilder     : Ignore the empty nacos configuration and get it based on dataId[gulimall-coupon] & group[dev]

2020-06-25 00:04:13.681  INFO 17936 --- [           main] b.c.PropertySourceBootstrapConfiguration :
Located property source: [
BootstrapPropertySource {name='bootstrapProperties-gulimall-coupon.properties,dev'}, 
BootstrapPropertySource {name='bootstrapProperties-gulimall-coupon,dev'}, 
BootstrapPropertySource {name='bootstrapProperties-other.yml,dev'}, 
BootstrapPropertySource {name='bootstrapProperties-mybatis.yml,dev'}, 
BootstrapPropertySource {name='bootstrapProperties-datasource.yml,dev'}]

五、网关gateway-88

动态上下线:发送请求需要知道商品服务的地址,如果商品服务器有123服务器,1号掉线后,还得改,所以需要网关动态地管理,它能从注册中心中实时地感知某个服务上线还是下线。【先通过网关,网关路由到服务提供者】

拦截:请求也要加上询问权限,看用户有没有权限访问这个请求,也需要网关。

所以我们使用spring cloud的gateway组件做网关功能。

网关是请求流量的入口,常用功能包括路由转发,权限校验,限流控制等。springcloud gateway取代了zuul网关。

https://spring.io/projects/spring-cloud-gateway

参考手册:https://cloud.spring.io/spring-cloud-gateway/2.2.x/reference/html/

三大核心概念:

  • Route: The basic building block of the gateway. It is defined by an ID, a destination URI, a collection of predicates断言, and a collection of filters. A route is matched if the aggregate predicate is true.发一个请求给网关,网关要将请求路由到指定的服务。路由有id,目的地uri,断言的集合,匹配了断言就能到达指定位置,
  • Predicate断言: This is a Java 8 Function Predicate. The input type is a Spring Framework ServerWebExchange. This lets you match on anything from the HTTP request, such as headers or parameters.就是java里的断言函数,匹配请求里的任何信息,包括请求头等。根据请求头路由哪个服务
  • Filter: These are instances of Spring Framework GatewayFilter that have been constructed with a specific factory. Here, you can modify requests and responses before or after sending the downstream request.过滤器请求和响应都可以被修改。

客户端发请求给服务端。中间有网关。先交给映射器,如果能处理就交给handler处理,然后交给一系列filer,然后给指定的服务,再返回回来给客户端。

有很多断言。

spring:
      cloud:
        gateway:
          routes:
          - id: after_route
            uri: https://example.org
            predicates:
            - Cookie=mycookie,mycookievalue

-代表数组,可以设置Cookie等内容。只有断言成功了,才路由到指定的地址。

spring:
     cloud:
       gateway:
         routes:
         - id: after_route
           uri: https://example.org
           predicates:
           - name: Cookie
             args:
               name: mycookie
               regexp: mycookievalue

创建,使用initilizer,Group:com.atguigu.gulimall,Artifact: gulimall-gateway,package:com.atguigu.gulimall.gateway。 搜索gateway选中。

pom.xml里加上common依赖, 修改jdk版本,

在gateway服务中开启注册服务发现@EnableDiscoveryClient,配置nacos注册中心地址applicaion.properties。这样gateway也注册到了nacos中,其它服务就能找到nacos,网关也能通过nacos找到其它服务

spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
spring.application.name=gulimall-gateway
server.port=88

bootstrap.properties 填写nacos配置中心地址

spring.application.name=gulimall-gateway
    
spring.cloud.nacos.config.server-addr=127.0.0.1:8848
spring.cloud.nacos.config.namespace=bfa85f10-1a9a-460c-a7dc-efa961b45cc1

本项目在nacos中的服务名

spring:
    application:
        name: gulimall-gateway

再去nacos里创建命名空间gateway(项目与项目用命名空间隔离),然后在命名空间里创建文件guilmall-gateway.yml

在项目里创建application.yml,根据条件转发到uri等

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>.*),/$\{segment}
    
            - id: third_party_route
              uri: lb://gulimall-third-party
              predicates:
                - Path=/api/thirdparty/**
              filters:
                - RewritePath=/api/thirdparty/(?<segment>.*),/$\{segment}
    
            - id: member_route
              uri: lb://gulimall-member
              predicates:
                - Path=/api/member/**
              filters:
                - RewritePath=/api/(?<segment>.*),/$\{segment}
    
            - id: ware_route
              uri: lb://gulimall-ware
              predicates:
                - Path=/api/ware/**
              filters:
                - RewritePath=/api/(?<segment>.*),/$\{segment}
    
            - id: admin_route
              uri: lb://renren-fast
              predicates:
                - Path=/api/**
              filters:  # 这段过滤器和验证码有关,api内容缓存了/renren-fast,还得注意/renren-fast也注册到nacos中
                - RewritePath=/api/(?<segment>.*),/renren-fast/$\{segment}
    
    
    
      ## 前端项目,/api前缀。开来到网关后断言先匹配到,过滤器修改url,比如跳转到renren微服务,所以要注意renren后端项目也注册到 nacos里
    ## 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

测试 localhost:8080/hello?url=baidu

网关使用的是Netty

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表