服务治理: Eureka
1、搭建Eureka服务器
1.1、创建项目添加依赖,创建项目是选择cloud discovery/eureka server
1.2、在主类上添加注解
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication
1.3、添加Eureka相关的配置信息
server.port=8761当前应用的端口号
eureka.client.fetch-registry=false是否需要抓取注册信息
eureka.client.register-with-eureka=false是否需要注册自己的信息
eureka.client.service-url.default-zone=http://localhost:8761/eureka/对应的eureka的地址
1.4、验证:
为浏览器中输入http://localhost:8761/
2、创建服务的提供者项目,具体的开发步骤和SpringBoot的SSM开发一致
2.1、创建项目时额外添加的依赖 选择cloud discovery/eureka discovery
2.2、在主类上添加注解
@SpringBootApplication
@EnableEurekaClient
public class BooksApplication
2.3、添加配置信息
eureka.client.service-url.default-zone=http://localhost:8761/eureka/
spring.application.name=BookProvider
2.4、先启动Eureka Server,然后启动服务提供者,在Eureka的web管理页面上可以看见当前服务的注册信息
Application Status
BOOKPROVIDER 192.168.20.8:BookProvider
3、创建服务的消费者项目,具体的开发步骤和SpringBoot的web开发一致
3.1、创建项目时额外添加的依赖 选择cloud discovery/eureka discovery
3.2、在主类上添加注解
@SpringBootApplication
@EnableEurekaClient
public class BooksApplication{
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
3.3、添加配置信息
eureka.client.service-url.default-zone=http://localhost:8761/eureka/
spring.application.name=BookConsumer
3.4、在控制器中通过名称查找指定的服务实例信息,例如地址、端口号等
@Controller
@RequestMapping("/books")
public class BookController {
@Autowired
private EurekaClient eurekaClient;
@Autowired
private RestTemplate restTemplate;
@GetMapping("")
public String list(Model model) throws Exception {
InstanceInfo info = eurekaClient.getNextServerFromEureka("BOOKPROVIDER", false);
String uri = info.getHomePageUrl();
Book[] blist = restTemplate.getForObject(uri + "/v1.0/books/", Book[].class);
model.addAttribute("bookList", blist);
System.out.println(blist);
return "books/list";
}
商品管理
(商品编号、商品名称、价格、图片、商品描述)
以信息为中心的表述性状态转移(Representational State Transfer,REST)已经称为替代传统SOAP Web 服务的流行方案.SOAP关注的一般是行为和处理,而REST关注的是要处理的数据.从Spring3.0开始,Spring为创建Rest API提供了良好的支持.
RestTemplate
借助RestTemplate,Spring应用能够方便地使用REST资源.Spring的RestTemplate访问使用了模版方法的设计模式.
delete()在特定的URL上对资源执行HTTP DELETE操作
exchange()在URL上执行特定的HTTP方法,返回包含对象的ResponseEntity,这个对象是从响应体中映射得到的
execute()在URL上执行特定的HTTP方法,返回一个从响应体映射得到的对象
getForEntity()发送一个HTTP GET请求,返回的ResponseEntity包含了响应体所映射成的对象
getForObject()发送一个HTTP GET请求,返回的请求体将映射为一个对象
getForEntity(String url, Class responseType, Object... urlVariables):该方法提供了三个参数,其中url为请求的地址,responseType为请求响应体,body的包装类型,urlVariables为url中的参数绑定。GET请求的参数绑定通常使用url中拼接的方式,比如http://USER-SERVICE/user?name=did可以像这样自已将参数拼接到url中,但更好的方法是在url中使用占位符并配合urlVariables参数实现GET请求的参数绑 定,比如url定义 为http://USER-SERVICE/user?name={l),然后可以这样来调用:getForEntity("http://USER-SERVICE/user?name={l}", String.class, "ddi")其中第三个参数didi会替换url中的{1}占位符。这里需要注意的是由于urlVariables参数是一个数组,所以它的顺序会对应url中 占位符定义的数字顺序。
getForEntity(String url, Class responseType, Map urlVariables):该方法提供的参数中只有urlVariables的参数类型与上面的方法不同。这里使用了Map类型,所以使用该方法进行参数绑定时需要在占位符中指定Map中参数的key值,比如url定义为http://USER-SERVICE/user?name={name),在Map类型urlVariables中就需要put一个key为name的参数来绑定url中{name}占位符的值
postForEntity()POST数据到一个URL,返回包含一个对象的ResponseEntity,这个对象是从响应体中映射得到的
postForObject()POST数据到一个URL,返回根据响应体匹配形成的对象
put() PUT 资源到特定的URL
Eureka是Netflix开发的服务发现组件,本身是一个基于REST的服务,主要用于定位运行在AWS域中的中间层服务,以达到负载均衡和中间层服务故障转移的目的。Spring cloud将它集成在子项目spring-cloud-netflix中,以实现Spring Cloud的服务发现功能。
Eureka 客户端的服务注册
Eureka客户端在运行时会向Eureka服务端发送周期性的心跳,Eureka 服务端利用客户端周期性的心跳续约请求来保证注册表的实时性。其中客户端会向服务端提供一个如果多久没有向服务端发送心跳请求,就不再维护这个客户端的时间阈值。
Eureka 客户端的服务拉取
在通常情况下服务提供者可能为多个实例,服务调用者在调用服务时通过一些特定的负载均衡算法来把请求转发到该服务调用者的实例上。Eureka 客户端也会定时向服务端获取最新的服务列表,如果想保证实时性更高的话需要修改其配置来实现。
Eureka-Client接口
提供多种方法获取应用集合Applications和应用实例信息集合InstanceInfo。提供方法获取本地客户端信息,如应用管理器ApplicationInfoManager和Eureka-Client配置EurekaClientConfig。提供方法注册本地客户端的健康检查和 Eureka 事件监听器。
InstanceInfo info=eurekaClient.getNextServerFromEureka("provider",false);false表示是否使用https
String uri=info.getHomePageUrl();
restTemplate.getForObject(uri+"/show",List.class);
DiscoveryClient接口
实现EurekaClient接口,用于与Eureka-Server交互。实现方法:1、向Eureka-Server注册自身服务。2、向Eureka-Server续约自身服务。3、向Eureka-Server取消自身服务,当关闭时。4、从Eureka-Server查询应用集合和应用实例信息
将org.springframework.cloud.client.discovery.DiscoveryClient;对象注入。消费者的第二方法:的唯一区别就是启动类上注解变成了@EnableDiscoveryClient,声明这是一个Eureka Client
List list = discoveryClient.getInstances("bookprovider");
System.out.println("discoveryClient.getServices().size() = " + discoveryClient.getServices().size());
for (String s : discoveryClient.getServices()) {
System.out.println("services " + s);
List serviceInstances = discoveryClient.getInstances(s);
for (ServiceInstance si : serviceInstances) {
System.out.println("services:" + s + ":getHost()=" + si.getHost());
System.out.println("services:" + s + ":getPort()=" + si.getPort());
System.out.println("services:" + s + ":getServiceId()=" + si.getServiceId());
System.out.println("services:" + s + ":getUri()=" + si.getUri());
System.out.println("services:" + s + ":getMetadata()=" + si.getMetadata());
}
}
System.out.println(list.size());
if (list != null && list.size() > 0) {
System.out.println(list.get(0).getUri());
}
discoveryClient.getServices().size() = 2
services bookprovider
services:bookprovider:getHost()=localhost
services:bookprovider:getPort()=8080
services:bookprovider:getServiceId()=BOOKPROVIDER
services:bookprovider:getUri()=http://localhost:8080
services:bookprovider:getMetadata()=vlsi.utils.CompactHashMap@7bac814d
1
http://localhost:8080
Eureka是单点的不适合于生产环境,那如何实现Eureka的高可用
Eureka Server都是单节点的,一旦该节点在生产中挂掉,就无法再提供服务的注册,为了保证注册中心的高可用,在生产中一般采用多节点的服务注册中心。
Eureka 的自我保护机制
Eureka在CAP理论当中是属于AP,也就说当产生网络分区时,Eureka保证系统的可用性,但不保证系统里面数据的一致性。默认情况下,当eureka server在一定时间内没有收到实例的心跳,便会把该实例从注册表中删除(默认是90秒),但是,如果短时间内丢失大量的实例心跳,便会触发eureka server的自我保护机制。在自我保护模式下eureka虽然收不到实例的心跳,但它认为实例还是健康的,eureka会保护这些实例,不会把它们从注册表中删掉。
保护机制的目的是避免网络连接故障,在发生网络故障时,微服务和注册中心之间无法正常通信,但服务本身是健康的,不应该注销该服务,如果eureka因网络故障而把微服务误删了,那即使网络恢复了,该微服务也不会重新注册到eureka server了,因为只有在微服务启动的时候才会发起注册请求,后面只会发送心跳和服务列表请求,这样的话,该实例虽然是运行着,但永远不会被其它服务所感知。所以eureka server在短时间内丢失过多的客户端心跳时,会进入自我保护模式,该模式下eureka会保护注册表中的信息,不在注销任何微服务,当网络故障恢复后,eureka会自动退出保护模式。自我保护模式可以让集群更加健壮。
在开发测试阶段,需要频繁地重启发布eureka.server.enable-self-preservation=false
在生产环境,不会频繁重启,所以,一定要把自我保护机制打开,否则网络一旦终端,就无法恢复。
Eureka在一个服务启动后最长需要2分钟才能被其它服务所感知
这是由三处缓存 + 一处延迟造成的
首先,Eureka对HTTP响应做了缓存。首先会去缓存中查询数据,如果没有则生成数据返回(即真正去查询注册列表),且缓存的有效时间为30s。也就是说,客户端拿到Eureka的响应并不一定是即时的,大部分时候只是缓存信息。
其次,Eureka Client对已经获取到的注册信息也做了30s缓存。即服务通过eureka客户端第一次查询到可用服务地址后会将结果缓存,下次再调用时就不会真正向Eureka发起HTTP请求了。
再次,负载均衡组件Ribbon也有30s缓存。Ribbon会从Eureka Client获取服务列表,然后将结果缓存30s。
最后,如果你并不是在Spring Cloud环境下使用这些组件(Eureka, Ribbon),你的服务启动后并不会马上向Eureka注册,而是需要等到第一次发送心跳请求时才会注册。心跳请求的发送间隔也是30s。
什么是Ribbon
Ribbon是Netflix发布的负载均衡器,它有助于控制HTTP和TCP的客户端的行为。为Ribbon配置服务提供者地址后,Ribbon就可基于某种负载均衡算法,自动地帮助服务消费者去请求。Ribbon默认提供了很多负载均衡算法,例如轮询、随机等。当然也可为Ribbon实现自定义的负载均衡算法。
在Spring Cloud中,当Ribbon与Eureka配合使用时,Ribbon可自动从Eureka Server获取服务提供者地址列表,并基于负载均衡算法,请求其中一个服务提供者实例。
@Autowired
private LoadBalancerClient loadBalancerClient;
ServiceInstance serviceInstance = this.loadBalancerClient.choose("BookProvider");
// 打印当前选择的是哪个节点
LOGGER.info("{}:{}:{}", serviceInstance.getServiceId(), serviceInstance.getHost(), serviceInstance.getPort());
Ribbon提供的主要负载均衡策略
简单轮询负载均衡RoundRobin
以轮询的方式依次将请求调度不同的服务器,即每次调度执行i = (i + 1) mod n,并选出第i台服务器。
随机负载均衡Random
随机选择状态为UP的Server
加权响应时间负载均衡WeightedResponseTime
一 个后台线程定期的从status里面读取评价响应时间,为每个server计算一个weight。Weight的计算也比较简单responsetime 减去每个server自己平均的responsetime是server的权重。当刚开始运行,没有形成statas时,使用roubine策略选择 server
区域感知轮询负载均衡ZoneAware
区域感知负载均衡内置电路跳闸逻辑,可被配置基于区域同源关系(Zone Affinity,也就是更倾向于选择发出调用的服务所在的托管区域内,这样可以降低延迟,节省成本)选择目标服务实例。它监控每个区域中运行实例的行 为,而且能够实时的快速丢弃一整个区域。这样在面对整个区域故障时,帮我们提升了弹性。
Ribbon自带负载均衡策略比较
BestAvailableRule
选择一个最小的并发请求的server。逐个考察Server,如果Server被tripped了,则忽略,在选择其中ActiveRequestsCount最小的server
AvailabilityFilteringRule
过滤掉那些因为一直连接失败的被标记为circuit tripped的后端server,并过滤掉那些高并发的的后端server(active connections 超过配置的阈值)。使用一个AvailabilityPredicate来包含过滤server的逻辑,其实就就是检查status里记录的各个server的运行状态
WeightedResponseTimeRule
根据相应时间分配一个weight,相应时间越长,weight越小,被选中的可能性越低。一 个后台线程定期的从status里面读取评价响应时间,为每个server计算一个weight。Weight的计算也比较简单responsetime 减去每个server自己平均的responsetime是server的权重。当刚开始运行,没有形成statas时,使用roubine策略选择 server。
RetryRule
对选定的负载均衡策略机上重试机制。在一个配置时间段内当选择server不成功,则一直尝试使用subRule的方式选择一个可用的server
RoundRobinRule
roundRobin方式轮询选择server。该策略实现了按照线性轮询的方式依次选择每个服务实例的功能。
RandomRule
随机选择一个server。该策略实现了从服务实例清单中随机选择一个服务实例的功能。
ZoneAvoidanceRule,默认值
复合判断server所在区域的性能和server的可用性选择server。使用ZoneAvoidancePredicate和AvailabilityPredicate来判断是否选择某个server,前一个判断判定一个 zone的运行性能是否可用,剔除不可用的zone(的所有server),AvailabilityPredicate用于过滤掉连接数过多的 Server。
ribbon已经默认实现了配置bean:
IRule ribbonRule: ZoneAvoidanceRule
ILoadBalancer ribbonLoadBalancer: ZoneAwareLoadBalancer
@Bean
public IRule ribbonRule() {
return new RandomRule();
}
什么是Feign
Feign是一个声明web服务客户端,这便得编写web服务客户端更容易,使用Feign创建一个接口并对它进行注解,它具有可插拔的注解支持包括Feign注解与JAX-RS注解,Feign还支持可插拔的编码器与解码器
Spring Cloud增加了对Spring MVC的注解,Spring Web 默认使用了HttpMessageConverters,Spring Cloud集成Ribbon和Eureka提供的负载均衡的HTTP客户端Feign
Feign开发
在应用主类中通过@EnableFeignClients注解开启Feign功能
使用@FeignClient("user-provider")注解来绑定该接口对应user-provider服务
@FeignClient("user-provider")
public interface UserClient {
@RequestMapping(method=RequestMethod.GET, value = "/detail")
public User getuserinfo();
通过Feign接口调用服务
@RestController
public class UserController {
@Autowired
UserClient userClient;
@RequestMapping(value = "/getuserinfo", method = RequestMethod.GET)
public User getuserinfo() {
return userClient.getuserinfo();
}
Feign总结
其实通过Feign封装了HTTP调用服务方法,使得客户端像调用本地方法那样直接调用方法,类似Dubbo中暴露远程服务的方式,区别在于Dubbo是基于私有二进制协议,而Feign本质上还是个HTTP客户端
什么是Hystrix
在分布式环境中,许多服务依赖项中的一些将不可避免地失败。Hystrix是一个库,通过添加延迟容差和容错逻辑来帮助您控制这些分布式服务之间的交互。Hystrix通过隔离服务之间的访问点,停止其间的级联故障以及提供回退选项,从而提高系统的整体弹性。
Hystrix用于执行操作
1:对通过第三方客户端库访问(通常通过网络)的依赖关系提供保护并控制延迟和故障。
2:隔离复杂分布式系统中的级联故障。
3:快速发现故障,尽快恢复。
4:回退,尽可能优雅地降级。
5:启用近实时监控,警报和操作控制。
为什么需要Hystrix?
大型分布式系统中,一个客户端或者服务依赖外部服务,如果一个服务宕了,那么由于设置了服务调用系统超时时间,势必会影响相应时间,在高并发的情况下大多数服务器的线程池就出现阻塞(BLOCK),影响整个线上服务的稳定性。
Hystrix解决什么问题
分布式架构中的应用程序具有几十个依赖关系,每个依赖关系在某个时刻将不可避免的出现异常。如果应用程序不与这些外部故障隔离,则可能出现线程池阻塞,引起系统雪崩。当使用Hystrix进行熔断后,每个依赖关系彼此隔离了,限制了当发生延迟时的阻塞。
Hystrix结合Feign使用
1、主类添加注解
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class FeignHystrixApplication
2、UserClient类
@FeignClient(value = "biz-service-0",fallback = UserClientHystrix.class)
public interface UserClient {
@RequestMapping(method = RequestMethod.GET, value = "/getuser")
public User getuserinfo();
@RequestMapping(method = RequestMethod.GET, value = "/getuser")
public String getuserinfostr();
3、创建熔断类UserClientHystrix
@Service
public class UserClientHystrix implements UserClient {
public User getuserinfo() {
throw new NullPointerException(" User getuserinfo() 服务不可用。。");
}
public String getuserinfostr() {
return " UserClientHystrix getuserinfostr() is fallback 服务不可用。。";
}
当网络出现异常的时候或直接跳转到这里实现类里面
zuul是什么
zuul是netflix开源的一个API Gateway服务器, 本质上是一个web servlet应用。Zuul在云平台上提供动态路由、监控、弹性、安全等边缘服务的框架。Zuul相当于是设备和Netflix流应用的Web网站后端所有请求的前门。
Zuul的主要功能是路由和过滤器。路由功能是微服务的一部分,比如/api/user映射到user服务,/api/shop映射到shop服务。zuul实现了负载均衡。
zuul能做什么?
Zuul可以通过加载动态过滤机制实现以下功能:
验证与安全保障: 识别面向各类资源的验证要求并拒绝那些与要求不符的请求。
审查与监控: 在边缘位置追踪有意义数据及统计结果,从而为我们带来准确的生产状态结论。
动态路由: 以动态方式根据需要将请求路由至不同后端集群处。
压力测试: 逐渐增加指向集群的负载流量,从而计算性能水平。
负载分配: 为每一种负载类型分配对应容量,并弃用超出限定值的请求。
静态响应处理: 在边缘位置直接建立部分响应,从而避免其流入内部集群。
多区域弹性: 跨越AWS区域进行请求路由,旨在实现ELB使用多样化并保证边缘位置与使用者尽可能接近。
Zuul开发
1、在入口类加上注解@EnableZuulProxy,开启zuul:
@EnableZuulProxy
@EnableEurekaClient
@SpringBootApplication
public class ServiceZuulApplication {
2、配置文件
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
server:
port: 8769
spring:
application:
name: service-zuul
zuul:
routes:
api-a:
path: /api-a/**
serviceId: service-ribbon
api-b:
path: /api-b/**
serviceId: service-feign
首先向eureka注册自己,端口为8769,服务名为service-zuul;以/api-a/开头的请求都指向service-ribbon;以/api-b/开头的请求都指向service-feign
服务过滤
zuul不仅只是路由,并且还能过滤,做一些安全验证
@Component
public class MyFilter extends ZuulFilter{
private static Logger log = LoggerFactory.getLogger(MyFilter.class);
@Override
public String filterType() {filterType返回一个字符串代表过滤器的类型,在zuul中定义了四种不同生命周期的过滤器类型,1、pre路由之前。2、routing路由之时。3、post路由之后。4、error发送错误调用
return "pre";
}
@Override
public int filterOrder() {过滤的顺序
return 0;
}
@Override
public boolean shouldFilter() {这里可以写逻辑判断,是否要过滤,true永远过滤
return true;
}
@Override
public Object run() {过滤器的具体逻辑。可用查sql,nosql去判断该请求到底有没有权限访问
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
log.info(String.format("%s >>> %s", request.getMethod(), request.getRequestURL().toString()));
Object accessToken = request.getParameter("token");
if(accessToken == null) {
log.warn("token is empty");
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(401);
try {
ctx.getResponse().getWriter().write("token is empty");
}catch (Exception e){}
return null;
}
log.info("ok");
return null;
}
}