微服务项目随着业务的增多,系统的不断扩大,微服务数量也逐渐增多,不利维护,服务治理是让服务自维护,解放开发者。Spring Cloud Eureka 是对Netflix公司的Eureka的二次封装,它实现了服务治理的功能。有服务端:服务的注册中心,客户端:服务的注册与发现。
服务发现框架,整个过程有三个角色:服务提供者,服务消费者,服务治理中心。
服务提供者EurekaClient:提供服务,并向注册中心注册,供消费者使用。即服务注册,它提供自身的元数据,比如IP地址、端口,运行状况等注册到EurekaServer中
服务消费者EurekaClient:需要使用一些服务的用户。有的服务消费者也需要向EurekaServer中注册,因为它也可能被其他消费者调用,这个时候它的角色也是提供者。
服务治理中心EurekaServer:相当于一个中介,搭建在服务提供者与消费者之间的桥梁,服务提供者把自己注册到服务中介那里,服务消费者如果需要服务就去中介那里获取服务列表,以调用已经在服务注册中心注册过的运行正常的服务
获取注册列表信息:Eureka客户端会从服务器获取注册信息并缓存在本地。该列表信息会定期更新一次(30秒)。每次获取缓存的信息可能有客户端在本地缓存的信息不同,Eureka客户端会自动处理。如果由于某种原因导致注册列表信息不能及时匹配,Eureka客户端会重新获取整个列表信息。
服务续约:Eureka客户端默认情况下会每隔30秒发送一次心跳来续约,告诉Eureka Service该我依然存在,没有问题,可以被其他服务正常调用。但是如果服务器90秒还没有收到某个服务的续约,它会将该服务实例从注册列表中删除。
自我保护机制:如果遇到某种因素,比如网络波动,Eureka的自我保护机制就是如果在15分钟内超过85%的客户端没有正常地发送心跳,那么Eureka Server就会认为客户端与治理中心之间出现了故障,从而启动自我保护机制。在Eurreka Server开启自我保护机制时:①不再从注册列表中移除长时间没有发送心跳的服务。②客户端任然能向治理中心注册和查询服务,但是只能当前节点可用,信息不会同步到其他节点,当网络回复或故障排除后在同步到其他节点,恢复正常工作。
通过在配置文件中配eureka.server.enable-self-preservation: true来开启自我保护机制。
服务下线: Eureka客户端需要关闭程序时会向服务器治理中心发送取消请求,发送后,该客户端实例将从服务器的实例注册表信息中删除。
服务剔除:有主动就有被动,客户端可以主动申请删除服务实例,那在默认情况下,服务器连续三个续约周期(90秒)没有收到客户端发来的心跳续约,Eureka服务器就会将该服务实例从服务注册列表中删除。
**本文示例涉及源码的下载地址**
:>>>github >>> csdn下载
本次微服务搭建的开发环境:Java8、maven 3.6.0、SpringBoot 2.1.14、SpringCloud Greenwich.SR1、MySql 5.7、Idea。
其中SpringBoot与SpringCloud有版本对应关系
,版本不对可能会导致依赖找不到(以踩坑)等问题,还有一个坑就是不同的SpringCloud的版本引入Eureka、hystrix等依赖名时的名称也不同。另外SpringCloud的版本号非常特别,使用的是伦敦地铁站的名称命名。
spring-boot-starter-parent | spring-cloud-dependencies |
---|---|
1.5.2.RELEASE | Dalston.RC1 |
1.5.4.RELEASE | Dalston.SR3 |
1.5.16.RELEASE | Edgware.SR5 |
1.5.20.RELEASE | Edgware.SR5 |
2.0.2.RELEASE | Finchley.BUILD-SNAPSHOT |
2.0.6.RELEASE | Finchley.SR2 |
2.1.4.RELEASE | Greenwich.SR1 |
1、创建一个SpringBoot项目
2、添加SpringCloud、Eureka相关依赖
<!-- 导入Spring Cloud的依赖管理 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<!-- 导入Eureka服务的依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
3.在启动类添加注解@EnableEurekaServer 申明这是一个eureka服务
4.配置application.yml
5.通过启动时动态传入端口号等信息部署两台Eureka Server,并且互相注册,实现高可用。
在idea中配置application.yml中的参数并启动:
6.访问
地址 http://127.0.0.1:6869/ http://127.0.0.1:6868/
负载均衡分为服务器端负载均衡和客户端负载均衡,是微服务中必须使用到的技术,通过负载均衡可以实现系统的高可用。负载均衡可以通过硬件和软件实现,比如,硬件有:F5、Array等,软件有:LVS,Nginx等。
如上图,用户请求先到达负载均衡,然后由负载均衡器根据负载均衡算法将请求转发到各个微服务。比如一个秒杀系统,为了高可用会做一个集群,就像上图一样,三个微服务都是秒杀系统服务,如果没有进行负载均衡,对其中某个服务进行大量请求,就很可能会导致这个系统奔溃,而其他两个系统基本不请求,就会变得鸡肋。所以负载均衡可以为微服务集群分担请求,减小系统压力。
常见的几种负载均很算法有:轮训、随机、加权轮训、加权随机、地址哈希等方法。
Ribbon是一个基于客户端的负载均衡工具,它从Eureka server获取服务列表,然后根据负载均衡算法请求到具体的服务。
Ribbon与Bginx一样都可以实现负载均衡。区别在于:
Ribbon
:
1.基于客户端的,先在客户端进行负载均衡才进行请求,在调用接口时,会在Eureka service 上获取服务列表并缓存在本地,然后在本地实现负载均衡策略,即根据负载均衡算法直接请求到具体的微服务。Eureka和zuul中默认是Ribbon负载均衡
。
2.应用场景:Ribbon适合在微服务中RPC远程调用本地服务的负载均衡,比如SpringCloud,Dubbo都是在本地进行负载均衡。
Nginx
:
1.基于服务端,请求先进入Nginx,然后再进行负载均衡调度。客户端将所有所有请求统一交给nginx,由nginx使用负载均衡算法进行请求转发。
2.应用场景:适合服务器端实现负载均衡的,比如Tomcat,Jetty。
microservice-user: #服务提供者服务名称
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
思路:开发一个用户信息服务(microservice-user)
作为服务提供者,部署两台并注册到EurekaServer中;开发一个用户登陆服务microservice-sso
,即服务消费者,并在本地使用Ribbon实现负载均衡,来调用用户服务。
服务提供者
SpringBoot与myBatis整合,开发一个通过用户名查找用户信息的方法作为用户信息服务
。
1.引入相关依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!--web核心依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--mysql数据库驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
2.配置文件和启动类
server:
port: ${
port} #服务端口
spring:
application:
name: microservice-user #指定服务名--用户信息服务
# mysql 属性配置
datasource:
driver-class-name: com.mysql.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
url: jdbc:mysql://127.0.0.1:3309/store?characterEncoding=utf-8&useSSL=false&serverTimezone=GMT%2B8
username: root
password: 123456
#spring集成Mybatis环境
#vo别名扫描包
mybatis:
type-aliases-package: com.user.demo.user.vo
mapperLocations: classpath*:mapper/**/*Mapper.xml
#executor-type: REUSE
eureka:
client:
registerWithEureka: true #是否将自己注册到Eureka服务中,默认为true
fetchRegistry: true #是否从Eureka中获取注册信息,默认为true
serviceUrl: #Eureka客户端与Eureka服务端进行交互的地址
defaultZone: http://127.0.0.1:6868/eureka/,http://127.0.0.1:6869/eureka/
eurekaServerConnectTimeoutSeconds: 60
eurekaServerReadTimeoutSeconds: 60
instance:
prefer-ip-address: true #将自己的ip地址注册到Eureka服务中
ip-address: 127.0.0.1
instance-id: ${
spring.application.name}:${
server.port} #指定实例id
lease-expiration-duration-in-seconds: 30 #续约更新时间间隔(默认30秒)
lease-renewal-interval-in-seconds: 10 # 续约到期时间(默认90秒)
leaseRenewalIntervalInSeconds: 10 #心跳时间
@EnableDiscoveryClient //注册到注册中心并从注册中心获取服务
@MapperScan("com.user.demo.user.dao")
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
3.dao与mapper
public interface UserDao {
UserVo getUser(String username);
}
<mapper namespace="com.user.demo.user.dao.UserDao">
<select id="getUser" resultType="UserVo">
SELECT uid as id,username,password,name,email,telephone,birthday,sex,state
FROM user WHERE username = #{
username}
</select>
</mapper>
4.service
public UserVo getUser(String username){
return userDao.getUser(username);
}
5.controller
@RestController
@RefreshScope
public class UserController {
@Autowired
private UserService userService;
@GetMapping(value = "user/{username}")
public UserVo getUser(@PathVariable("username") String username){
return userService.getUser(username);
}
}
消费者
本地实现了负载均衡的用户登录服务
并调用用户信息服务
1.引入SpringBoot,SpringCloud等相关依赖
2.配置文件
server:
port: ${
port} #服务端口
spring:
application:
name: microservice-sso #指定服务名---用户登录服务
eureka:
client:
registerWithEureka: true #是否将自己注册到Eureka服务中,默认为true
fetchRegistry: true #是否从Eureka中获取注册信息,默认为true
serviceUrl: #Eureka客户端与Eureka服务端进行交互的地址
defaultZone: http://127.0.0.1:6868/eureka/,http://127.0.0.1:6869/eureka/ #eureka地址
instance:
prefer-ip-address: true #将自己的ip地址注册到Eureka服务中
ip-address: 127.0.0.1
instance-id: ${
spring.application.name}:${
server.port} #指定实例id
3.启动类中添加RestTemplate,使用**@LoadBalanced**注解
@EnableFeignClients
@EnableDiscoveryClient //从eureka server中获取注册列表
@SpringBootApplication
public class SsoserverApplication {
public static void main(String[] args) {
SpringApplication.run(SsoserverApplication.class, args);
}
@Bean // 向Spring容器中定义RestTemplate对象
@LoadBalanced //取出服务列表进行负载均衡算法
public RestTemplate restTemplate() {
return new RestTemplate();
}
Service与Controller
@Service
public class SsoService {
@Autowired
private RestTemplate restTemplate;
public UserVo checkLogin(String username,String password){
String serviceId = "microservice-user"; //用户信息服务的服务名
//请求用户服务中查找用户方法的url
String url = "http://" + serviceId + "/user/" + username;
UserVo userVo = restTemplate.getForObject(url,UserVo.class);
if (userVo!=null){
String pw = userVo.getPassword();
if (password.equals(pw)){
return userVo;
}
}
return null;
}
}
@RestController
@RefreshScope
public class SsoController {
@Autowired
private SsoService ssoService;
/**
* 登录接口
* @param username 账号
* @param password 密码
* @return
*/
@GetMapping(value = "login/{username}/{password}")
public ResultData userLogin(@PathVariable("username") String username, @PathVariable("password") String password) {
UserVo userVo = ssoService.checkLogin(username,password);
if(userVo == null ){
return new ResultData(1,"登录失败",null);
}
return new ResultData(1,"登录成功",userVo);
}
服务提供者和消费者开发完成后,通过动态传入端口启动两台,并注册到EurekaServer中,访问EurekaServer查看微服务信息
访问消费者的url(我的消费者的url端口是8381和8382):http://localhost:8381/login/aaa/aaa或http://localhost:8382/login/aaa/aaa,刷新四次,通过控制台打印的sql发现第1次和第3次 用户服务1打印sql,第2次和第4次用户服务2打印sql,可以看到Ribbon默认的是轮询的负载均衡算法
。
在分布式环境中,不可避免的会有会有许多服务依赖项中的某些失败。比如有个服务A调用了服务B,而服务B又调用了服务C,但因为某些原因,服务C扛不住了,这时大量的请求就会在服务C处阻塞。这时调用服务C的服务B因为接受不到C的响应,也会出现阻塞,同理,B阻塞了,调用它的A也会阻塞,因为这些阻塞的请求会消耗系统的IO、线程等资源,所以系统会因为扛不住也崩了,这就是服务雪崩。Hystrix提供的熔断、降级等能解决这一问题,提高系统弹性,避免雪崩。
Spring Cloud Hystrix是基于Netflix的开源框架Hystrix的整合,实现了断路器、线程隔离、信号隔离、Fallback、监控等功能。能够保证系统在一个依赖或者多个依赖出险问题时依然可用。
熔断:Hystrix中的断路器模式,可以使用@HystrixCommand注解来标注这个方法达到断路器的效果,这样,在调用这个方法时如果超过指定时间(默认是1000ms,当然也可以自定义,自己对这个注解的属性进行设置),断路器就会中断对这个方法的调用。
降级:当一个方法调用异常时,通过调用另一个方法来给用户一个友好的反馈,比如一个商品查询的服务由于访问量大或其他原因扛不住了,这是就会调用另一个方法,告诉用户“当前人数太多,请稍后再试”。通常使用注解@HystrixCommand(fallbackMethod = “xxx”)来实现降级处理,xxx是降级处理时调用的备用方法。
1.在上面Ribbon实战的基础上添加Hystrix依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
2.在启动类添加@EnableHystrix 注解
在方法上添加注解@HystrixCommand(fallbackMethod = “xxx”),并编写对应的降级处理方法。
开发完后,停掉用户信息服务,再访问用户登录服务就会出现 “网络出错,请稍后再试” 。
服务网关就是在服务的前面设置一道屏障,对要过来的请求做过滤,校验,路由等处理,校验不通过的请求将被拒绝访问,通过的再将它路由到对应的微服务,提高了微服务的安全性。
Spring Cloud Zuul是整合了Netflix公司的Zuul开源项目实现的微服务网关,有请求路由、负载均衡、校验过虑等功能。网关有的功能,zull基本上都有,其中最关键的就是**路由(Router)和过滤(Filter)**了。
1.新建SpringBoot项目,添加zull依赖。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
2.启动类添加注解@EnableZuulProxy
server:
port: ${
port} #服务端口 这里zull的端口是6677
spring:
application:
name: microservice-api-gateway #zull网关服务名
eureka:
client:
registerWithEureka: true #是否将自己注册到Eureka服务中,默认为true
fetchRegistry: true #是否从Eureka中获取注册信息,默认为true
serviceUrl: #Eureka客户端与Eureka服务端进行交互的地址
defaultZone: http://127.0.0.1:6868/eureka/,http://127.0.0.1:6869/eureka/
instance:
prefer-ip-address: true #将自己的ip地址注册到Eureka服务中
ip-address: 127.0.0.1
instance-id: ${
spring.application.name}:${
server.port} #指定实例id
查看现在的服务注册中心:
现在注册中心有用户信息服务(microservice-user)和登录服务(microservice-sso)
将zull服务注册到EurekaServer中可以获取到注册中心的服务列表,这样以前访问登录服务(microservice-sso)的请求http://localhost:8382/login/aaa/aaa
就可以变成http://localhost:6677/microservice-sso/login/aaa/aaa
了。
以前访问用户信息服务(microservice-user)的请求http://localhost:8681/user/aaa
就可以变成http://localhost:6677/microservice-user/user/aaa
了。
zuul:
routes:
sso-service: #sso-service登录服务
path: /sso/** #配置请求URL的请求规则
serviceId: microservice-sso
进行上面的配置 访问路径就变成了 http://localhost:6677/sso/login/aaa/aaa
,请求就会都被转发到microservice-sso
zuul:
ignore-patterns: **/sso/**
*匹配一级路径
** 可以匹配多级路径
这样配置,带有sso的请求就会被屏蔽过滤掉。
zuul:
ignored-services: '*'
routes:
microservice-user: /userserver/**
这样配置可以忽略所有代理,只保留自己的配置,现在只能访问用户信息服务,并且只能使用user这种路径来访问
不能访问:
http://localhost:6677/microservice-sso/login/aaa/aaa
http://localhost:6677/microservice-user/login/aaa
能访问
http://localhost:6677/userserver/login/aaa
上面是屏蔽所有,还可以指定屏蔽
zuul:
ignored-services: microservice-sso
routes:
user-service: /userserver/**
此时只有用户登录服务被屏蔽掉了,对服务microservice-sso的请求不会成功
能访问,但用户信息服务能正常访问。
http://localhost:6677/userserver/login/aaa
http://localhost:6677/microservice-user/user/aaa
写一个过滤类 继承ZuulFilter
在这里插入代码片@Slf4j
@Component //加入到Spring容器
public class UserLoginZuulFilter extends ZuulFilter {
//具体的业务逻辑
@Override
public Object run() {
RequestContext requestContext = RequestContext.getCurrentContext();
HttpServletRequest request = requestContext.getRequest();
log.info("Method:{} URI:{}",request.getMethod(),request.getRequestURI());
Object token = request.getParameter("loginToken");
if (token == null){
log.info("loginToken is null");
requestContext.setSendZuulResponse(false); //过滤掉该请求,不对其进行路由转发
requestContext.setResponseStatusCode(401); //设置返回的错误码x
}else {
log.info("loginToken is not null");
}
return null;
}
// 该过滤器是否需要执行
@Override
public boolean shouldFilter() {
return true;
}
/**
* 过滤器类型
* pre:请求前执行
* Routing:路由过滤器
* Post:在Response 相应之前执行过滤
* @return
*/
@Override
public String filterType() {
return "pre"; // 设置过滤器类型为:pre
}
// 设置执行顺序 越小越先执行
@Override
public int filterOrder() {
return 0;
}
}
访问http://localhost:6677/sso/login/aaa/aaa
无法访问,因为没有loginToken,请求被过滤掉,后台日志:
正确访问http://localhost:6677/sso/login/aaa/aaa?loginToken=xxx
到这里,初步了解了SpringCloud中的一些组件,并搭建了一个简单的微服务架构:
**源码地址**
:>>>github >>> csdn下载