架构演变过程
1.单一架构
- 我们最先接触的单体架构,整个系统就只有一个工程,打包往往是打成了 war 包,然后部署到单一tomcat 上面,这种就是单体架构
- 请求–>APP–>DB
- 假如系统按照功能划分了,商品模块,购物车模块,订单模块,物流模块等等模块。那么所有模块都会在一个工程里面,这就是单体架构
- 好处
- 维护成本低
- 缺点
- 一台服务器满足不了需求,最薄弱的环节是DB
2.在单一架构基础上横向扩展(多个APP)
-
这种架构貌似暂时解决了我们的问题,但是用户量慢慢增加后,我们只能通过横向加机器来解决,还是会存在版本迭代慢,代码维护困难的问题。改变点如下:
- 1.分流软件nginx进行分流,负载到不同的服务器上,分流软件有nginx、lvs、haproxy
- 2.在DB之前加一层Redis
-
访问最多的是商品模块,只是查询,实际只需要扩展商品模块,而现在是整个工程都扩容了,这无形中是一种资源的浪费,因为其他模块可能根本不需要扩容就可以满足需求。
-
缺点
- 1.横向扩展机器,所有的模块都增加了,是一种资源浪费
- 2.代码维护成本高,只修改一个模块的某一个类,则所有的机器都要重新发布,因此版本发布会变得非常谨慎,迭代会变得非常缓慢
-
因此,需要按照功能对应用程序进行划分,由一个工程变成4个工程,模块拆分后,模块和模块之间是需要通过接口调用的方式进行通信,模块和模块之间通过分
流软件nginx进行负载均衡。- 这种架构的缺点
- 1.不能灵活扩容或者下线。一旦需要横向加机器,或者减机器都需要修改nginx配置,虽然nginx可以通过reload优雅停机,但是也要修改配置文件,随着业务模块增加,使用nginx实现负载均衡的维护成本变得非常高
- 2.没有实时监控整个系统的健康。模块之间调用的哪台机器也是未知的,机器是否健康都是未知的,无法坚持每一台机器是否都是可用的
- 这种架构的缺点
3.SOA服务治理框架
- SOA框架就是为了解决不能灵活扩容或者下线,以及没有实时监控整个系统的健康的问题而产生的
- 基于注册中心的 SOA 框架,扩展是非常方便的,因为不需要维护分流工具,但我们启动应用的时候就会把服务通过 http 的方式注册到注册中心。
- 在 SOA 框架中一般会有三种角色:1、注册中心 2、服务提供方 3、服务消费方
- 因此SOA框架的优点
- 1.扩容变得简单,服务下线变得简单,没有任何人工干预工作量
- 2.有系统实时监控,实时把控每一个端点的调用情况
- 3.运行时干预服务调用,对于服务消费方,可以人工干预不让其调用某一台服务提供方,也可以手动控制服务的超时时间
- 同样,SOA框架也会带来相应的缺点
- 1.以前只有一个war包,变成上百个war包,导致整个项目的部署变得非常复杂,必须要借助CI(持续集成,典型的是Jenkins)工具
- 2.分布式事务问题,接口设计变得讲究,特殊场景要有幂等设计,并且要支持分布式事务
- 3.网络抖动带来的接口超时,这是不可控的
- 在做技术选型的时候,微服务带来的好处?
- 1.整个团队分工明确
- 2.代码管理简单了,项目迭代变得频繁
- 3.QPS每一个模块都不大
注册中心
-
在注册中心维护了服务列表
-
ip、端口、服务名称,通过这些信息RPC服务就可以调用到
-
注册中心的作用?
1.服务启动后,就会把服务注册到注册中心,不需要手动修改nginx配置
2.服务消费方启动后,会去加载注册中心的服务列表,加载到本地,并缓存,问题变成了选择哪一个去调用
-
-
注册作用引入带来的好处?
- 扩容变得简单,不需要手动修改nginx文件,扩容后把服务信息注册到服务列表,同时会把服务增加的信息,通知给该服务的每一个调用方
- 服务下线也是相同的,通过心跳机制,如果没有相应,会从服务列表中删除相应的服务,通知通知服务调用方,同样不涉及任何配置文件的修改
服务提供方
- 服务提供方启动的时候会把自己注册到注册中心
服务消费方
- 服务消费方启动的时候,把获取注册中心的服务列表,然后调用的时候从这个服务列表中选择某一个去调用。
搭建微服务治理框架springcloud
- springcloud的框架基础是springboot,所以必须引入springboot
搭建注册中心–eureka服务端
-
以eureka为例,除了eureka,还可以用nacos、redis
扫描二维码关注公众号,回复: 13117103 查看本文章 -
业务场景:商品模块调用购物车模块
- 商品模块需要从eureka调用服务----服务发现
- 购物车模块把服务注册到eureka----服务注册
-
服务消费方、服务注册方和注册中心两两之间都是通过http协议调用的
1.创建一个子model
- new一个maven-archetype-quickstart项目
- springcloud-eureka
2.导入jar包
-
<properties> <java.version>1.8</java.version> <spring-cloud.version>Hoxton.SR1</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> </dependencies> <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> </dependencies> </dependencyManagement> <build> <finalName>micro-eureka</finalName> <plugins> <!--打包可执行的jar --> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>2.2.2.RELEASE</version> <configuration> <mainClass>com.xiangxue.jack.EurekaApplication</mainClass> </configuration> </plugin> <plugin> <groupId>com.spotify</groupId> <artifactId>docker-maven-plugin</artifactId> <version>0.4.13</version> <configuration> <imageName>jack/microEureka</imageName> <dockerDirectory>${project.basedir}/src/main/docker</dockerDirectory> <resources> <resource> <targetPath>/</targetPath> <directory>${project.build.directory}</directory> <include>${project.build.finalName}.jar</include> </resource> </resources> </configuration> </plugin> </plugins> </build>
关键jar包
-
<spring-cloud.version>Hoxton.SR1</spring-cloud.version>
-
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency>
-
<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency>
-
<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> </dependencies> </dependencyManagement>
3.启动类
-
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; @SpringBootApplication @EnableEurekaServer public interface EurekaApplication { public static void main(String[] args) { SpringApplication.run(EurekaApplication.class,args); } }
-
@SpringBootApplication
- 这个注解里面会有@EnableAutoConfiguration这个核心配置注解
-
@EnableEurekaServer
- 开启eurekaServer服务注册功能,代表这是eureka的服务端
-
通用写法
-
public static void main(String[] args) { SpringApplication.run(EurekaApplication.class,args); }
-
搭建购物车模块—服务提供方(也是eureka客户端)
1.新建一个model
- new一个maven-archetype-quickstart项目
- micro-order
2.导入jar包
-
<properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.7</maven.compiler.source> <maven.compiler.target>1.7</maven.compiler.target> <java.version>1.8</java.version> <spring-cloud.version>Hoxton.SR1</spring-cloud.version> </properties> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.2.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <!-- 把mybatis的启动器引入 --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.0.0</version> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> <version>LATEST</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>javax.ws.rs</groupId> <artifactId>javax.ws.rs-api</artifactId> <version>2.0</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.37</version> </dependency> <dependency> <groupId>com.xiangxue.jack</groupId> <artifactId>micro-service-api</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.springframework.retry</groupId> <artifactId>spring-retry</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bus-amqp</artifactId> </dependency> </dependencies> <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> </dependencies> </dependencyManagement> <build> <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) --> <plugins> <!-- clean lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle --> <plugin> <artifactId>maven-clean-plugin</artifactId> <version>3.1.0</version> </plugin> <!-- default lifecycle, jar packaging: see https://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging --> <plugin> <artifactId>maven-resources-plugin</artifactId> <version>3.0.2</version> </plugin> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.0</version> </plugin> <plugin> <artifactId>maven-surefire-plugin</artifactId> <version>2.22.1</version> </plugin> <plugin> <artifactId>maven-jar-plugin</artifactId> <version>3.0.2</version> </plugin> <plugin> <artifactId>maven-install-plugin</artifactId> <version>2.5.2</version> </plugin> <plugin> <artifactId>maven-deploy-plugin</artifactId> <version>2.8.2</version> </plugin> <!-- site lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle --> <plugin> <artifactId>maven-site-plugin</artifactId> <version>3.7.1</version> </plugin> <plugin> <artifactId>maven-project-info-reports-plugin</artifactId> <version>3.0.0</version> </plugin> </plugins> </pluginManagement> <plugins> <!--打包可执行的jar --> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <mainClass>com.xiangxue.jack.MicroOrderApplication</mainClass> </configuration> </plugin> </plugins> <resources> <resource> <directory>src/main/java</directory> <includes> <include>**/*.xml</include> </includes> </resource> <resource> <directory>src/main/resources</directory> <includes> <include>**.*</include> </includes> </resource> </resources> </build>
关键jar包
-
<spring-cloud.version>Hoxton.SR1</spring-cloud.version>
-
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
-
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
-
<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> </dependencies> </dependencyManagement>
3.创建bootstrap.properties
-
spring.application.name=micro-order server.port=8084 eureka.client.serviceUrl.defaultZone=http\://localhost\:8763/eureka/ #服务续约,心跳的时间间隔 eureka.instance.lease-renewal-interval-in-seconds=30 #如果从前一次发送心跳时间起,90秒没接受到新的心跳,讲剔除服务 eureka.instance.lease-expiration-duration-in-seconds=90 #表示eureka client间隔多久去拉取服务注册信息,默认为30秒 eureka.client.registry-fetch-interval-seconds=30 mybatis.typeAliasesPackage=com.xiangxue.jack.bean mybatis.mapper-locations=classpath:com/xiangxue/jack/mapper/commomMapper.xml spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://127.0.0.1:3306/consult?serverTimezone=UTC spring.datasource.username=root spring.datasource.password=123456 #sql日志 logging.level.com.xiangxue.jack.dao=debug spring.cloud.config.profile=dev spring.cloud.config.label=master #这种配置是configserver还单机情况,直接连接这个单机服务就行 spring.cloud.config.uri=http://localhost:8085/ #configserver高可用配置 #开启configserver服务发现功能 #spring.cloud.config.discovery.enabled=true #服务发现的服务名称 #spring.cloud.config.discovery.service-id=config-server #如果连接不上获取配置有问题,快速响应失败 spring.cloud.config.fail-fast=true #默认重试的间隔时间,默认1000ms spring.cloud.config.retry.multiplier=1000 #下一间隔时间的乘数,默认是1.1 #spring.cloud.config.retry.initial-interval=1.1 #最大间隔时间,最大2000ms spring.cloud.config.retry.max-interval=2000 #最大重试次数,默认6次 spring.cloud.config.retry.max-attempts=6 spring.rabbitmq.host=192.168.67.139 spring.rabbitmq.port=5672 spring.rabbitmq.username=admin spring.rabbitmq.password=admin # 刷新配置url http://localhost:8081/actuator/bus-refresh spring.cloud.bus.refresh.enabled=true spring.cloud.bus.trace.enabled=true management.endpoints.web.exposure.include=*
4.启动类
-
package com.xiangxue.jack; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; @SpringBootApplication(scanBasePackages = { "com.xiangxue.jack" }) // 注册到eureka @EnableEurekaClient @MapperScan("com.xiangxue.jack.dao") public class MicroOrderApplication { public static void main(String[] args) { SpringApplication.run(MicroOrderApplication.class, args); } }
5.controller
-
package com.xiangxue.jack.controller; import com.xiangxue.jack.bean.ConsultContent; import com.xiangxue.jack.service.UserService; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.env.Environment; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletRequest; import java.util.List; @RestController @RequestMapping("/user") public class UserController { private org.slf4j.Logger logger = LoggerFactory.getLogger(getClass()); @Autowired UserService userService; @Value("${username}") private String username; @Value("${redis.password}") private String redispass; @Autowired Environment environment; @RequestMapping("/queryContent") public List<ConsultContent> queryContent(HttpServletRequest request) { logger.info("==================已经调用==========" + request.getRemotePort()); logger.info("@Value======username======" + username); logger.info("Environment======username======" + environment.getProperty("username")); logger.info("@Value======redispass======" + redispass); logger.info("Environment======redispass======" + environment.getProperty("redis.password")); return userService.queryContent(); } }
6.启动客户端
- 此时在eureka的注册列表中,新增了一个MICRO-ORDER的服务,对应的状态是UP,以及服务的ip、端口、名称
7.打jar包–再启动一个购物车
-
在micro-order项目的pom文件增加
-
<packaging>jar</packaging>
-
-
在该项目下执行mvn package -Dmaven.test.skip=true
-
之后在jar包目录下执行java -jar micro-order-1.0-SNAPSHOT.jar --serve
r.port=8741-
如果报错没有主清单属性?
-
<plugins> <!--打包可执行的jar --> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <mainClass>com.xiangxue.jack.MicroOrderApplication</mainClass> </configuration> </plugin> </plugins>
-
-
-
此时在eureka中micro-order,又会增加一个实例,端口号是8741
搭建商品模块–服务调用方(也是eureka客户端)
1.新建一个model
-
new一个maven-archetype-quickstart项目
- micro-web
2.导入jar包
- 与购物车相同
3.创建bootstrap.properties
- 与购物车类似
4.启动类
-
import com.xiangxue.jack.service.feign.StudentService; import com.xiangxue.jack.service.feign.TeacherServiceFeign; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestTemplate; @SpringBootApplication(scanBasePackages = { "com.xiangxue.jack"}) //注册到eureka @EnableEurekaClient //开启断路器功能 @EnableCircuitBreaker //开启feign支持,clients指定哪个类开启feign @EnableFeignClients(clients = { StudentService.class,TeacherServiceFeign.class}) public class MicroWebApplication { @Bean @LoadBalanced RestTemplate restTemplate() { return new RestTemplate(); } public static void main(String[] args) { SpringApplication.run(MicroWebApplication.class,args); } }
-
其中RestTemplate restTemplate()是请求http调用的客户端,@LoadBalanced是负载均衡的注解
5.controller
-
package com.xiangxue.jack.controller; import com.xiangxue.jack.bean.ConsultContent; import com.xiangxue.jack.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.List; @RestController @RequestMapping("/user") public class UserController { @Autowired private UserService userService; @RequestMapping("/queryUser") public List<ConsultContent> queryUser() { return userService.queryContents(); } @RequestMapping("/queryMonitor") public String queryMonitor() { return userService.queryMonitor(); } }
6.UserService
-
package com.xiangxue.jack.service; import com.alibaba.fastjson.JSONObject; import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty; import com.xiangxue.jack.bean.ConsultContent; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.ScopedProxyMode; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; @Slf4j @Service @Scope(proxyMode = ScopedProxyMode.INTERFACES) public class UserServiceImpl implements UserService { public static String SERVIER_NAME = "micro-order"; @Autowired private RestTemplate restTemplate; @Override public List<ConsultContent> queryContents() { s.incrementAndGet(); List<ConsultContent> results = restTemplate.getForObject("http://" + SERVIER_NAME + "/user/queryContent", List.class); return results; } }
-
其中restTemplate.getForObject方法是一个get请求,是通过SERVIER_NAME来找到相应的服务,因为已经在eureka中注册了,就会从服务列表若干个实例中选择一个来调用
7.启动服务调用方
-
发现eureka中会增加micro-web的实例
-
在服务调用方也加了eureka客户端的注解,在服务调用方的本地就会有服务提供方的列表,同样的在服务提供方本地就会有服务调用发的列表
8.负载均衡测试
- 多调用localhost:8083/user/queryUser几次,因为在服务调用方里面,是以服务名称调用的,会有负载均衡处理,可以发现并不是一直请求一个实例,那两个实例都有被调用到,在那两个实例中都有打印调用结果
服务手动下线
-
delete请求
-
http://localhost:8763/eureka/apps/MICRO-ORDER/localhost:micro-order:8084
-
8763是eureka注册中心
-
MICRO-ORDER是服务名
-
localhost:micro-order:8084是具体的实例
-
cap结论
- zookeeper是cp,zab协议保证一致性,但是没有可用性,当投票选举中有一半节点挂了就不可用了
- eureka是ap,是可用的,但是不满足一致性,100个节点,99个节点挂了,只要有一个节点可用,仍然能正常工作