Skip to content

Microservice based on Springcloud,follwer me to build a microservice instance step by step/跟着我一步一步的搭建一个基于springcloud的微服务实例

Notifications You must be signed in to change notification settings

liuyong520/microservicecloud

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

10 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Table of Contents generated with DocToc

microservicecloud

Microservice based on Springcloud,follwer me to build a com.styz.com.styz.microservice instance step by step

插件推荐

在搭建项目之前先推荐两款很好用的IDEA插件,至于插件做什么用的,自己百度吧! 1.MyBatisCodeHelper-Pro

2.lombok

建立父工程Microservicecloud

点击 File——>new——>Project 新建maven工程项目 修改pom.xml文件

<dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>5.1.30</version>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-devtools</artifactId>
                <version>2.0.3.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.security</groupId>
                <artifactId>spring-security-test</artifactId>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>${lombok.version}</version>
            </dependency>
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid</artifactId>
                <version>1.1.0</version>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <repositories>
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
        <repository>
            <id>repository.springframework.maven.release</id>
            <name>Spring Framework Maven Release Repository</name>
            <url>http://maven.springframework.org/milestone/</url>
        </repository>
        <repository>
            <id>org.springframework</id>
            <url> http://maven.springframework.org/snapshot</url>
        </repository>
        <repository>
            <id>spring-milestone</id>
            <name>Spring Maven MILESTONE Repository</name>
            <url>http://repo.spring.io/libs-milestone</url>
        </repository>
        <repository>
            <id>spring-release</id>
            <name>Spring Maven RELEASE Repository</name>
            <url>http://repo.spring.io/libs-release</url>
        </repository>
    </repositories>

搭建Employ员工服务

创建数据库

1.执行microserivcecloud-database中的Employ.sql文件创建员工服务需要用到的表 当然也可以自己考出来执行

use db;
SET STORAGE_ENGINE = InnoDB;

DROP TABLE IF EXISTS `employ`;
CREATE TABLE `employ` (
  `id` bigint(20) NOT NULL COMMENT '员工编号',
  `name` varchar(48) NOT NULL COMMENT '名字',
  `departmentId` bigint(10) NOT NULL COMMENT '部门编号',
  `datasourceId` varchar(64) NOT NULL COMMENT '数据存储位置',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
insert into employ(id,name,departmentId,datasourceId) values(1,"zhansan",1,DATABASE());
insert into employ(id,name,departmentId,datasourceId) values(2,"lisi",2,DATABASE());
insert into employ(id,name,departmentId,datasourceId) values(3,"wangwu",3,DATABASE());
insert into employ(id,name,departmentId,datasourceId) values(4,"maliu",4,DATABASE());

2.创建springboot+mybatis整合项目 2.1创建microservicecloud-api 子模块项目用于存放公共的接口和实体 修改pom.xml文件

 <!--添加lombok依赖-->
 <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>

2.2利用MyBatisCodeHelper-Pro生成mybatis的mapper接口和数据库实体到microservicecloud-api中 生成结果如下

com.styz.microservicecloud.entity.Employ #实体
com.styz.microservicecloud.mapper.EmployMapper #mapper接口
microservicecloud-api/src/main/resources/mapperxml/EmployMapper.xml #mapper.xml sql 文件

然后执行mvn install操作 2.3创建microservicecloud-employservice 模块 修改pom文件

<dependencies>
    <dependency>
        <groupId>com.com.styz.microservicecloud</groupId>
        <artifactId>microservicecloud-api</artifactId>
        <version>${project.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
    </dependency>

    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
    </dependency>
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.1</version>
    </dependency>
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>1.3.0</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
</dependencies>

创建com.styz.microservicecloud包新建EmployServiceApplication启动类

@SpringBootApplication
@MapperScan("com.com.styz.microservicecloud.mapper")
public class EmployServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(EmployServiceApplication.class);
    }
}

然后创建com.styz.com.styz.microserivcecloud.service.EmployService接口以及其实现类

public interface EmployService {
    /**
     * 获取所有员工
     * @return
     */
    public List<Employ> getAllEmployes();

    /**
     * 根据Id获取指定员工
     * @param id
     * @return
     */
    public Employ getEmployById(long id);
}
@Service
public class EmployServiceImpl implements EmployService {
    @Autowired
    private EmployMapper employMapper;

    public List<Employ> getAllEmployes() {
        return employMapper.listAll();
    }

    public Employ getEmployById(long id) {
        return employMapper.selectByPrimaryKey(id);
    }
}

然后创建com.styz.com.styz.microserivcecloud.controller.EmployController

@RestController
@RequestMapping("/employ")
public class EmployController {
    @Autowired
    private EmployService employService;

    @RequestMapping("/getAllEmployes")
    public List<Employ> getAllEmployes(){
        return employService.getAllEmployes();
    }
    @RequestMapping("/getEmployById/{id}")
    public Employ getEmployById(@PathVariable("id")long id){
        return employService.getEmployById(id);
    }
}

添加配置文件

#应用程序使用的端口
server:
  port: 8999
#mybatis相关配置
mybatis:
  config-location: classpath:mybatis/mybatis.cfg.xml
  type-alias-package: com.com.styz.microservicecloud.entity
  mapper-locations:
    - classpath*:mapperxml/*Mapper.xml
#数据源配置
spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://k8s-n1:3307/db
    username: root
    password: 123456
    dbcp2:
      min-idle: 5
      initial-size: 5
      max-idle: 10
      max-total: 20
      max-wait-millis: 10
# 服务名称
  application:
    name: EmployService
    

mybatis/mybatis.cfg.xml

<?xml version="1.0" encoding="UTF-8" ?>
        <!DOCTYPE configuration
                PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
                "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <settings>
        <!--二级缓存开启-->
        <setting name="cacheEnabled" value="true"/>
    </settings>
</configuration>

**注意:@MapperScan("com.com.styz.microservicecloud.mapper")**必须扫描到API模块里的mapper接口

2.5 自嗨一下 访问http://localhost:8999/employ/getAllEmployes 查看一下是否正常返回 自此单机服务已经整合完毕

创建消费者服务microservicecloud-employconsummer

1.新建模块microservicecloud-employconsummer修改pom文件

<dependencies>
    <dependency>
        <groupId>com.com.styz.microservicecloud</groupId>
        <artifactId>microservicecloud-api</artifactId>
        <version>${project.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
    </dependency>
</dependencies>

2.添加配置application.yml

server:
  port: 8111
spring:
  application:
    name: EmployComsummer
  devtools:
    restart:
      enabled: true

3.新建config配置类com.styz.com.styz.com.styz.microservice.config.ConsummerConfigBean

@Configuration
public class ConsummerConfigBean {
   /**
    * RestTemplate 封装了httpclient的实现能够简化我们对请求的访问
    * @return 
    */
    @Bean
    public RestTemplate getRestTemplate(){
        return new RestTemplate();
    }
} 

4.创建消费者controller com.styz.com.styz.com.styz.microservice.controller.EmployConsumerController

@RestController
@RequestMapping("/comsummer")
public class EmployConsumerController {
    //服务提供方的地址:http://localhost:8999/employ/getAllEmployes
    public static final String EMPLOY_PROVIDE_URL="http://localhost:8999/employ";
    @Autowired
    private RestTemplate restTemplate;

    @RequestMapping("/getById/{id}")
    public Employ getEmployById(@PathVariable("id") long id){
        return restTemplate.postForObject(EMPLOY_PROVIDE_URL+"/getEmployById/"+id,null,Employ.class);
    }
    @RequestMapping("/getAllEmployes")
    public List<Employ> getAllEmployes(){
        return restTemplate.postForObject(EMPLOY_PROVIDE_URL+"/getAllEmployes",null,List.class);
    }
}

5.新建消费者启动类:com.styz.com.styz.com.styz.microservice.EmployComsumerApplication

@SpringBootApplication
public class EmployComsumerApplication {
    public static void main(String[] args) {
        SpringApplication.run(EmployComsumerApplication.class);
    }
}

至此单体消费者实例已经完成。自嗨一下:访问http://localhost:8111/consummer/getById/1 看看能不能调用到服务端 提供的服务

Eureka注册中心搭建

Eureka主要的功能就是服务注册与发现。是Netflix公司下开源一款产品,功能比较单一 基于AP原则实现。cs架构模式实现,想要详细了Eureka可以百度。

单机模式

创建microservicecloud-registry-8211 模块

1.修改pom文件

 <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <finalName>${project.name}</finalName>
                </configuration>
            </plugin>
        </plugins>
    </build>

2.创建com.styz.com.styz.com.styz.microservice.RegistryApplication_8211 启动类

@SpringBootApplication
@EnableEurekaServer
public class RegistryApplication_8211 {
    public static void main(String[] args) {
        SpringApplication.run(RegistryApplication_8211.class,args);
    }
}

3.配置文件

spring:
  application:
    name: registry-8211

eureka:
  instance:
    #euraka 服务器实例名称
    #主机名字需要修改host文件
    hostname: registry8211
  client:
    #false表示不向注册中心注册自己
    register-with-eureka: false
    #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
    fetch-registry: false
    service-url:
      #单机配置 http://${eureka.instance.hostname}:${server.port}/eureka
      #集群环境搭建:http://registry8212:8212/eureka/,http://registry8213:8213/eureka/
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka
server:
  port: 8211

单机注册中心已经完成 访问 http://registry8211:8211/ 就会出现注册中心的界面

集群搭建

修改microservicecloud-registry-8211 模块

1.只需要修改bootstrap.yml中的

 defaultZone: http://registry8212:8212/eureka/,http://registry8213:8213/eureka/

拷贝microservicecloud-registry-8211 两份

microservicecloud-registry-8212 microservicecloud-registry-8213 同理修改

 
spring:
  application:
    name: registry-8212

eureka:
  instance:
    #euraka 服务器实例名称
    hostname: registry8212
  client:
    #false表示不向注册中心注册自己
    register-with-eureka: false
    #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
    fetch-registry: false
    service-url:
      #单机配置 http://${eureka.instance.hostname}:${server.port}/eureka
      #集群环境搭建
      defaultZone: http://registry8211:8211/eureka/,http://registry8213:8213/eureka/
server:
  port: 8212
spring:
  application:
    name: registry-8213

eureka:
  instance:
    #euraka 服务器实例名称
    hostname: registry8213
  client:
    #false表示不向注册中心注册自己
    register-with-eureka: false
    #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
    fetch-registry: false
    service-url:
      #单机配置 http://${eureka.instance.hostname}:${server.port}/eureka
      #集群环境搭建
      defaultZone: http://registry8211:8211/eureka/,http://registry8212:8212/eureka/
server:
  port: 8213

修改hosts文件

127.0.0.1  registry8213 registry8212 registry8211

至此集群模式搭建完毕,可以分别访问 http://registry8211:8211/ http://registry8212:8212/ http://registry8213:8213/ 出现Eureka的界面表示成功。

注册服务到注册中心

新建microservicecloud-employproviderwithregistry

拷贝microserivicecloud-employservice中的代码和配置文件到microservicecloud-employproviderwithregistry 模块中 修改pom文件

<!--添加注册中心依赖-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--添加springboot监控依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

添加bootstrap.yml文件

#服务名称
spring:
  application:
    name: EmployService
eureka:
  client:
    service-url:
      #单机配置:http://localhost:8211/eureka
      defaultZone: http://registry8212:8212/eureka/,http://registry8213:8213/eureka/,http://registry8211:8211/eureka/
  # 修改注册服务的名字
  instance:
    instance-id: EmployService-8999
    # 防问路径可以显示IP地址
    prefer-ip-address: true
# 添加服务Info信息
info:
  app.name: EmployService-8999
  app.describution: 员工服务
  company.name: com.com.styz
  author.name: liuy
  build.artifactId: $project.artifactId$
  build.version: $project.version$

修改启动类com.styz.com.styz.microserivcecloud.EmployServiceApplication 添加@EnableEurekaClient注解

@SpringBootApplication
@MapperScan("com.com.styz.microservicecloud.mapper")
@EnableEurekaClient
public class EmployServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(EmployServiceApplication.class,args);
    }
}

然后可以查看注册中心是否有服务注册进去了。

消费者调用

修改microservicecloud-employconsummer中的com.styz.com.styz.com.styz.microservice.controller.EmployConsumerController 中的

// EMPLOYSERVICE为服务名称
public static final String EMPLOY_PROVIDE_URL="http://EMPLOYSERVICE/employ";

修改pom添加eureka客户端依赖

 <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

修改启动类添加服务发现注解@EnableDiscoveryClient

@SpringBootApplication
@EnableDiscoveryClient
public class EmployComsumerApplication {
    public static void main(String[] args) {
        SpringApplication.run(EmployComsumerApplication.class,args);
    }
}

修改yml文件

eureka:
  client:
    service-url:
      #单机配置:http://localhost:8211/eureka
      defaultZone: http://registry8212:8212/eureka/,http://registry8213:8213/eureka/,http://registry8211:8211/eureka/
    register-with-eureka: false

此时访问消费者 就能够完成消费者 调用 服务端服务 的调用了

服务集群搭建

为了介绍下面的resttemplate+ribbon 和feign负载均衡,需要将服务作集群搭建。 基本架构是,每个服务访问自己独立的数据库,数据上构造一定的差异,这样方便我们测试负载均衡。

执行Employ-cluster.sql文件中的sql

创建三个不同的库,同样的表。然后插入不同的数据。

拷贝microservicecloud-employproviderwithregistry 三份

分别为microservicecloud-employ-provider-8911 microservicecloud-employ-provider-8912 microservicecloud-employ-provider-8913 具体修改内容见代码。

创建microservicecloud-employ-comsumer-robbin 模块

1.修改pom文件

<!-- 添加 ribbon依赖-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

修改启动类

@SpringBootApplication
@EnableDiscoveryClient
//指定某个服务用某种负载均衡算法
@RibbonClient(name = "EMPLOYSERVICE",configuration = MyRules.class)
public class EmployComsumerApplication {
    public static void main(String[] args) {
        SpringApplication.run(EmployComsumerApplication.class,args);
    }
}

com.styz.rules.MyRules

@Configuration
public class MyRules {
    @Bean
    @Primary
    public IRule getRobbinRule(){
        return new RandomRule();
    }
}

新建自定义负载均衡算法 com.styz.com.styz.com.styz.microservice.robbin.CountRobbinRule

public class CountRobbinRule implements IRule {
    private ILoadBalancer loadBalancer;
    private AtomicInteger count = new AtomicInteger(0);
    private AtomicInteger serverIndex = new AtomicInteger(0);
    @Override
    public Server choose(Object o) {
        List<Server> allServers = loadBalancer.getAllServers();
        while (true){
            if(count.get() > 5){
                if(serverIndex.incrementAndGet() > allServers.size()){
                    serverIndex.set(serverIndex.get()%allServers.size());
                }
                count.set(0);
            }else {
                count.incrementAndGet();
                return allServers.get(serverIndex.get());
            }
        }
    }

    @Override
    public void setLoadBalancer(ILoadBalancer iLoadBalancer) {
        loadBalancer = iLoadBalancer;
    }

    @Override
    public ILoadBalancer getLoadBalancer() {
        return loadBalancer;
    }
}

修改配置文件类com.styz.com.styz.com.styz.microservice.config.ConsummerConfigBean

@Configuration
public class ConsummerConfigBean {
    /**
     * @LoadBalanced 注解会使用robbin负载均横算法。默认是轮训,如需修改得显示声明对应的算法。
     * 如果没有声明,默认就是轮训
     * robbin 提供7种轮训算法,我们可以手动扩展实现负载均衡算法。
     */
    @Bean
    @LoadBalanced
    public RestTemplate getRestTemplate(){
        RestTemplate restTemplate = new RestTemplate();
        return restTemplate;
    }
}

启动注册中心,启动服务提供者集群,启动消费者,访问http://localhost:7111/consummer/getById/1 查看负载均衡情况。

引入Feign负载均衡

RestTemplate+robbin是基于微服务名称封装httpclient的方式完成的服务调用。

而Feign是在此基础上又进行了一次封装,使得消费端可以通过注解+接口的方式 用接口的方式完成服务的调用,有点类似于Dubbo中接口调用一样。只是底层实现不一样, Dubbo是RPC,而Feign是http的Restful调用。

创建microservicecloud-employ-consummer-feign模块

修改pom文件添加Feign支持

<dependencies>
        <dependency>
            <groupId>com.com.styz.microservicecloud</groupId>
            <artifactId>microservicecloud-api</artifactId>
            <version>${project.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
    </dependencies>

2.修改microservicecloud-api 添加Feign接口com.styz.microservicecloud.service.EmployClientService

@FeignClient(value = "EMPLOYSERVICE")
public interface EmployClientService {
    //注意此时的url是服务端的全路径,不是客户端哦
    @RequestMapping(value = "/employ/getAllEmployes",method = RequestMethod.POST)
    public List<Employ> getAllEmployes();

    @RequestMapping(value = "/employ/getEmployById/{id}",method = RequestMethod.POST)
    public Employ getEmployById(@PathVariable("id")long id);

}

修改com.styz.com.styz.com.styz.microservice.controller.EmployConsumerController

@RestController
@RequestMapping("/comsummer")
public class EmployConsumerController {
    //EmployClientService 为Feign接口
    @Autowired
    private EmployClientService clientService;

    @RequestMapping("/getById/{id}")
    public Employ getEmployById(@PathVariable("id") long id){
       return clientService.getEmployById(id);
    }
    @RequestMapping("/getAllEmployes")
    public List<Employ> getAllEmployes(){
        return clientService.getAllEmployes();
    }
}

修改启动类com.styz.com.styz.com.styz.microservice.EmployComsumerApplication

@SpringBootApplication
@EnableDiscoveryClient
//注意EnableFeignClients 一定要扫描到@FeignClient的包否则会报错
@EnableFeignClients(basePackages = "com.styz.microservicecloud.service")
public class EmployComsumerApplication {
    public static void main(String[] args) {
        SpringApplication.run(EmployComsumerApplication.class,args);
    }
}

此时再次访问http://localhost:7111/consummer/getById/1 查看负载均衡情况

添加熔断Hystrix支持

Hystrix是什么?请自行百度。

新建microservicecloud-employ-consummer-hystrix 模块

修改pom文件

<dependencies>
        <dependency>
            <groupId>com.com.styz.microservicecloud</groupId>
            <artifactId>microservicecloud-api</artifactId>
            <version>${project.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>

修改启动类添加

@SpringBootApplication
@EnableDiscoveryClient
//注意EnableFeignClients 一定要扫描到@FeignClient的包否则会报错
@EnableFeignClients(basePackages = "com.styz.microservicecloud.service")
//添加熔断机制
@EnableHystrix
@ComponentScan("com.styz")
public class EmployComsumerApplication {
    public static void main(String[] args) {
        SpringApplication.run(EmployComsumerApplication.class,args);
    }
}

修改controller 添加@HystrixCommand, 一般情况下熔断一般实现在服务提供端,我这里只是demo就实现在消费端了。 @HystrixCommand能捕获异常可以根据异常不同,进行不同的熔断操作。

@RestController
@RequestMapping("/comsummer")
public class EmployConsumerController {
    @Autowired
    private EmployClientService clientService;

    @RequestMapping("/getById/{id}")
    @HystrixCommand(fallbackMethod = "processEmployException")
    public Employ getEmployById(@PathVariable("id") long id){
        Employ employ = clientService.getEmployById(id);
        if(null == employ){
            throw new RuntimeException("未找到对应ID的员工!");
        }
        return employ;
    }

    /**
     * 这个方法可以写在服务端,也可以写在消费端,如果异常导致的最好写在服务端,如果非异常,可以实现在消费端
     * 方法写在服务端叫熔断,写在消费端叫降级。
     * HystrixCommand 注解能实现的熔断,但是缺点非常明显,容易导致方法膨胀:也就是每个方法都需要写一个异常方法
     * 方法会非常多,不便与维护。
     *
     * @param id
     * @return
     */
    public Employ processEmployException(@PathVariable("id") long id){
        Employ employ = new Employ();
        employ.setId(id);
        employ.setName("未找到对应ID的员工!");
        return employ;
    }
    @RequestMapping("/getAllEmployes")
    public List<Employ> getAllEmployes(){
        return clientService.getAllEmployes();
    }
}

@HystrixCommand 业务代码耦合严重,方法容易膨胀,所以有了Fegin熔断机制 可以在接口上面实现熔断,从而解藕 修改com.styz.microservicecloud.service.EmployClientService

/**
 * 服务降级处理:
 * 1.添加fallbackFactory 或者Fallback类
 * 2.开启服务降级:
 * feign:
 *   hystrix:
 *     enabled: true
 * 注意fallbackFactory类或者Fallback类要能被扫描到。否则不会生效。
 */

@FeignClient(value = "EMPLOYSERVICE",fallbackFactory = EmployClientServiceFallbackFactory.class)
public interface EmployClientService {
    //注意此时的url是服务端的全路径,不是客户端哦
    @RequestMapping(value = "/employ/getAllEmployes",method = RequestMethod.POST)
    public List<Employ> getAllEmployes();

    @RequestMapping(value = "/employ/getEmployById/{id}",method = RequestMethod.POST)
    public Employ getEmployById(@PathVariable("id")long id);

}
@Component
public class EmployClientServiceFallbackFactory implements InitializingBean,FallbackFactory<EmployClientService> {

    @Override
    public void afterPropertiesSet() throws Exception {

    }

    public static class EmployClientServiceFallback implements EmployClientService{

        @Override
        public List<Employ> getAllEmployes() {
            ArrayList<Employ> employs = new ArrayList<>();
            employs.add(new Employ().setName("********暂停服务*****"));
            System.out.println("*************暂停服务****************");
            return employs;
        }

        @Override
        public Employ getEmployById(long id) {
            return new Employ(id,"该ID不存在",0L,"");
        }
    }

    @Override
    public EmployClientService create(Throwable throwable) {
        return new EmployClientServiceFallback();
    }
}

开启Fegin降级机制

feign:
  hystrix:
    enabled: true

此时称之为Feign+fallback 服务降级 何谓服务降级:简单的说就是当整个系统资源不够用时,为了满足某些服务依然可用,就需要忍痛将其他服务停掉,将系统资源释放 用以支持其他服务。此时停掉的服务不能影响整个系统的使用,就需要在消费端在访问不到服务的情况下,将服务进行降级处理。

启动消费者测试一下服务的熔断和降级看看。

添加监控HystrixDashboard支持

创建microservicecloud-employ-consummer-hystrixDashboard模块 修改pom文件

<dependencies>
        <dependency>
            <groupId>com.com.styz.microservicecloud</groupId>
            <artifactId>microservicecloud-api</artifactId>
            <version>${project.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>
    <build>
        <finalName>${project.artifactId}</finalName>
        <resources>
            <resource>
                <directory>src/main/resource</directory>
                <filtering>true</filtering>
            </resource>
        </resources>
        <plugins>
            <!--添加actor/info信息的描述-->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-resources-plugin</artifactId>
                <configuration>
                    <delimiters>
                        <delimiter>$</delimiter>
                    </delimiters>
                </configuration>
            </plugin>
        </plugins>
    </build>

修改启动类:

@SpringBootApplication
@EnableDiscoveryClient
//注意EnableFeignClients 一定要扫描到@FeignClient的包否则会报错
@EnableFeignClients(basePackages = "com.styz.microservicecloud.service")
//添加熔断机制
@EnableHystrix
@ComponentScan("com.styz")
//添加服务监控 1.服务端要添加对应的包spring-boot-starter-actuator
@EnableHystrixDashboard
public class EmployComsumerApplication {
    public static void main(String[] args) {
        SpringApplication.run(EmployComsumerApplication.class,args);
    }
}

hystrix.stream

springboot2.0中hystrix.stream url出现404错误问题解决办法1 添加配置类com.styz.com.styz.com.styz.microservice.config.HystrixConfig

@Configuration
public class HystrixConfig {
    @Bean
    public HystrixMetricsStreamServlet hystrixMetricsStreamServlet(){
        return new HystrixMetricsStreamServlet();
    }

    @Bean
    public ServletRegistrationBean registration(HystrixMetricsStreamServlet servlet){
        ServletRegistrationBean registrationBean = new ServletRegistrationBean();
        registrationBean.setServlet(servlet);
        //是否启用该registrationBean
        registrationBean.setEnabled(true);
        registrationBean.addUrlMappings("/hystrix.stream");
        return registrationBean;
    }
}

修改yml文件

#需要暴露的url:
#这里暴露了/actuator/hystrix.stream,/actuator/info
management:
  endpoints:
    web:
      exposure:
        include:
          - hystrix.stream
          - info
# 添加服务Info信息
info:
  app.name: ${eureka.instance.instance-id}
  app.describution: 带熔断监控器的消费者
  company.name: com.com.styz
  author.name: liuy
  build.artifactId: $project.artifactId$
  build.version: $project.version$

访问 http://localhost:7112/hystrix 页面,出现豪猪图片表示成功 访问 http://localhost:7112/hystrix/hystrix.stream 出现ping 字眼表示成功 http://localhost:7112/hystrix 这个页面如何使用,请自行百度吧。

前面使用的/hystrix.stream端点监控单个微服务。然而在使用微服务架构的应用系统一般会包含多个微服务,每个微服务通常都会部署多个实例。如果每次只能查看单个实例的监控数据,就必须在Hystrix Dashboard上切换想要监控的地址,这显然很不方便。

Turbine简介

Turbine是一个聚合Hystrix监控数据的工具,它可将所有相关/hystrix.stream端点的数据聚合到一个组合的/turbine.stream中,从而让集群的监控更加方便

使用Turbine监控多个微服务

创建microservicecloud-employ-consummer-hystrixDashborad-turbine 修改pom文件

<!--添加Turbine依赖-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-turbine</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-netflix-turbine</artifactId>
</dependency>

修改启动类添加@EnableTurbine注解

@SpringBootApplication
@EnableDiscoveryClient
//注意EnableFeignClients 一定要扫描到@FeignClient的包否则会报错
@EnableFeignClients(basePackages = "com.styz.microservicecloud.service")
//添加熔断机制
@EnableHystrix
@ComponentScan("com.styz")
//添加服务监控 1.服务端要添加对应的包spring-boot-starter-actuator
@EnableHystrixDashboard
@EnableTurbine
public class EmployConsummerTurbineApplication {
    public static void main(String[] args) {
        SpringApplication.run(EmployConsummerTurbineApplication.class,args);
    }
}

添加turbine配置

turbine:
  # app-config表示待监测的服务名称
  app-config: EmployComsummer-HystrixDashboard,EmployComsummer-HystrixDashboard-Turbine
  aggregator:
    cluster-config: default
  cluster-name-expression: new String("default")

turbine.appConfig :配置Eureka中的serviceId列表,表明监控哪些服务 turbine.aggregator.clusterConfig :指定聚合哪些集群,多个使用","分割,默认为default。可使用http://.../turbine.stream?cluster={clusterConfig之一}访问 turbine.clusterNameExpression :

  1. clusterNameExpression指定集群名称,默认表达式appName;此时:turbine.aggregator.clusterConfig需要配置想要监控的应用名称;
  2. 当clusterNameExpression: default时,turbine.aggregator.clusterConfig可以不写,因为默认就是default;
  3. 当clusterNameExpression: metadata['cluster']时,假设想要监控的应用配置了eureka.instance.metadata-map.cluster: ABC,则需要配置,同时turbine.aggregator.clusterConfig: ABC

测试

启动microservicecloud-employ-consummer-hystrixDashboard 和microservicecloud-employ-consummer-hystrixDashborad-turbine 访问http://localhost:7113/hystrix,监控http://localhost:7113/turbine.stream 后然后分别访问 http://localhost:7112/comsummer/getById/1http://localhost:7113/comsummer/getById/1 查看监控面板的变化。

添加网关Zuul支持

创建microservicecloud-gateway-zuul 修改pom文件

<dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-config</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-actuator</artifactId>
        </dependency>
    </dependencies>

配置:

eureka:
  client:
    service-url:
      #单机配置:http://localhost:8211/eureka
      defaultZone: http://registry8212:8212/eureka/,http://registry8213:8213/eureka/,http://registry8211:8211/eureka/
    register-with-eureka: true
    fetch-registry: true
  instance:
    instance-id: GateWay
    prefer-ip-address: true
zuul:
  ignoredServices: '*'
  host:
    connect-timeout-millis: 20000
    socket-timeout-millis: 20000

  routes:
    employservice:
      path: /employ/**
      serviceId: employservice
      stripPrefix: false
      sensitiveHeaders:

    employcomsummer-hystrix:
      path: /consummer/**
      serviceId: employcomsummer-hystrix
      stripPrefix: false
      sensitiveHeaders:

    employcomsummer-hystrixdashboard:
      path: /dashboard/**
      serviceId: employcomsummer-hystrixdashboard
      stripPrefix: false
      sensitiveHeaders:

    baidu:
      path: /baidu/**
      url: http://www.baidu.com/

启动类添加

@SpringBootApplication
@EnableZuulProxy
@EnableDiscoveryClient
public class GatewayApplication {
    public static void main(String[] args) {
        SpringApplication.run(GatewayApplication.class,args);
    }
}

添加配置服务Config支持

新建microservicecloud-config模块

修改pom文件

<dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-server</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

创建com.styz.microservicecloud.ConfigServerApplication

@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ConfigServerApplication.class,args);
    }
}

修改配置文件

spring:
  cloud:
    config:
      server:
        native:
          search-locations: classpath:/config
#  本地模式
  profiles:
    active: native

server:
  port: 8888

此配置意思是config配置服务器扫描服务所在的config目录。将config目录下 的配置文件发不成restful接口。

启动com.styz.microservicecloud.ConfigServerApplication 然后 访问http://localhost:8888/config/dev 就能访问到application-dev.yml配置。 访问http://localhost:8888/employservice/default 就能访问到employsevice配置。

修改服务提供者

修改microservicecloud-employ-provider-8911 让其加载config服务器上的配置 1.删除原来的application.yml 2.添加依赖spring-cloud-starter-config 其实原来就有。

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

3.修改bootstrap.yml 添加如下内容

spring:
  application:
    name: EmployService
  cloud:
    config:
      uri: http://config:8888
      fail-fast: true

spring.cloud.config.url 配置的就是配置服务的地址。

重启服务端microservicecloud-employ-provider-8911 测试一下是否正常

采用spring.cloud.config.url 这种方式指定url的方式直接连接,有明显缺陷, 当配置服务器挂掉,无法加载远程配置。就有了将config服务器服务化,注册到 Eureka,利用服务发现的方式去找服务的地址,从而实现config地址动态变化,尽管 config服务器发生变化,服务端也无需在修改对应的url。同时config配置服务器可以做 集群注册到Eureka,从而也可避免单点故障。

配置中心config把配置放置到git仓库

之前配置放在本地了,现在将配置迁移到git仓库,好处是config服务做集群的时候可以统一从仓库拉取配置, 自己不需要维护配置文件。

创建存放配置的git目录

在microservicecloud文件夹下创建microservicecloud-config-dir目录 将配置文件放入此文件,提交github。

新建microservicecloud-config-git 模块

1.修改pom文件

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-server</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-actuator</artifactId>
        </dependency>
    </dependencies>

2.新建类com.styz.microservicecloud.GItConfigServerApplication

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

3.修改yml

spring:
  application:
    name: GitConfigServer
  cloud:
    config:
      # label 表示是哪个分支
      label: master
      server:
        git:
          # git用户密码私有仓库必填
          username:
          password:
          # uri 是git的地址
          uri: https://github.com/liuyong520/microservicecloud
          # search-paths 搜索git仓库哪个目录,相对地址
          search-paths: microservicecloud-config-dir

eureka:
  client:
    register-with-eureka: true
    service-url:
      defaultZone: http://registry8212:8212/eureka/,http://registry8213:8213/eureka/,http://registry8211:8211/eureka/
  instance:
    instance-id: GitConfigServer
    prefer-ip-address: true

server:
  port: 8888

server端就此改造完了

测试一下 访问http://localhost:8888/config/dev 就能访问到application-dev.yml配置。 访问http://localhost:8888/employservice/default 就能访问到employsevice配置。

修改microservicecloud-employ-provider-8911

采用服务发现的方式连接configsever。

spring:
  application:
    name: EmployService
  cloud:
    config:
      name: EmployService  #指定配置文件名称
      profile: default
      label: master
      discovery:
        enabled: true
        service-id: GitConfigServer #configserver服务

重启服务端看看是否正常启动。

添加链路跟踪

何谓链路跟踪? 在分布式系统中往往一次简单的请求,往往涉及到一系列的服务调用,服务A调用服务B,服务B调用服务C。这样 的服务链上如果某个服务发生故障,无法快速定外到故障点,从而无法快速定位问题。而链路 跟踪是就是跟踪整个服务之间的调用链,监控每个服务间的调用情况,以备在调用出现问题时,能快速定位问题。

zipKin 简介

Zipkin是一种分布式跟踪系统。它有助于收集解决服务体系结构中的延迟问题所需的计时数据。功能包括收集和查找此数据。

如果日志文件中有跟踪ID,则可以直接跳转到该文件。否则,您可以根据服务,操作名称,标签和持续时间等属性进行查询。将为您总结一些有趣的数据,例如在服务中花费的时间百分比,以及操作是否失败 Zipkin UI还提供了一个依赖关系图,显示了每个应用程序通过的跟踪请求数。这有助于识别聚合行为,包括错误路径或对已弃用服务的调用。

zipKin环境搭建

1.docker环境下:

docker run --name zipkin -d -p 9411:9411 openzipkin/zipkin

2.物理机

curl -sSL https://zipkin.io/quickstart.sh | bash -s
java -jar zipkin.jar

访问http://localhost:9411/ 出现zipkin UI界面 为了 演示一下链路跟踪需要搭建一个调用链环境,我这里用一个消费端和服务端来演示

修改服务提供者

1.新建microservicecloud-employ-provider-8911-sleuth 改造一下microservicecloud-employ-provider-8911 修改pom

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

修改yml文件

spring:
  application:
    name: EmployService
  #添加链路跟踪
  sleuth:
    web:
      client:
        enabled: true
    sampler:
      probability: 1.0
  zipkin:
    base-url: http://k8s-n3:9411/

至此服务端改造完成

修改消费者

1.新建microservicecloud-employ-consummer-hystrix-sleuth模块 改造一下microservicecloud-employ-consummer-hystrix 修改pom文件

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

然后修改一下yml文件

server:
  port: 7115
spring:
  application:
    name: EmployComsummer-Hystrix
  devtools:
    restart:
      enabled: true
  sleuth:
    web:
      client:
        enabled: true
    sampler:
      probability: 1.0 #采样比例 1表示100%全部采样
  zipkin:
    base-url: http://k8s-n3:9411/

消费端完成 启动服务端和消费端,访问http://localhost:7115/consummer/getById/1 调用一下消费者, 然后去http://localhost:9411/zipkin/ 查看一下链路跟踪情况。

简单说一下链路跟踪的原理:针对给个服务的调用,都进行来埋点,消息的发送时刻以及消息的接收, 都会被记录,以及包括服务名称,方法名称都被埋点探针记录下来,每次调用链上有唯一的tracId,这个是 全局唯一的,然后用spanId区分是调用链上的哪个服务的哪个方法。以上数据的采集是通过httpReport方式提交的 这种方式优点是简单直接,缺点是 1.网络问题:导致数据采集丢失 2。给zipkin带上访问上的压力量大可以压蹦zipkin 所以要改造一下:用消息队列进行解藕。就有了下面的sleuth+zipkin+rabbitmq方式,当然消息队列可以换成kafka也没有问题。

链路跟踪之sleuth+zipKin+rabbitmq

rabbitMq环境搭建

我这里用的是docker环境,所以直接贴出来docker下搭建过程。 拉取官方镜像,镜像地址:https://hub.docker.com/_/rabbitmq/ 拉取镜像:docker pull rabbitmq,如需要管理界面:docker pull rabbitmq:management 执行指令启动RabbitMQ 无管理界面:

docker run --localhost rabbit-host --name my_rabbit -d -p 5672:5672 rabbitmq

有管理界面:

docker run --localhost rabbit-host --name my_rabbit -d -p 5672:5672 -p 15672:15672 rabbitmq:management

账号:guest 密码:guest

调整之前的消费者和服务者

修改pom文件

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

修改yml文件

spring
    sleuth:
        web:
          client:
            enabled: true
        sampler:
          probability: 1.0
      zipkin:
        sender:
          type: rabbit
      rabbitmq:
        password: guest
        username: guest
        host: k8s-n3
        port: 5672
        virtual-host: /

修改zipkin服务端

docker run -d -p9411:9411 -e "RABBIT_ADDRESSES=rabbithost:5672" openzipkin/zipkin

如果你的rabbitMq用户名密码不是guest需要指定环境变量。

结果展示

enter description here enter description here

About

Microservice based on Springcloud,follwer me to build a microservice instance step by step/跟着我一步一步的搭建一个基于springcloud的微服务实例

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published