spring boot和spring cloud的依赖
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.2.6.RELEASEversion>
<relativePath/>
parent>
<properties>
<java.version>1.8java.version>
<spring-cloud.version>Hoxton.SR3spring-cloud.version>
properties>
启动类开启eureka服务
@EnableEurekaServer
eureka单点配置
server.port=7001
#是否将自己注册到Eureka Server,默认为true,由于当前就是server,故而设置成false,表明该服务不会向eureka注册自己的信息
eureka.client.register-with-eureka=false
#是否从eureka server获取注册信息,由于单节点,不需要同步其他节点数据,用false
eureka.client.fetch-registry=false
#设置服务注册中心的URL,用于client和server端交流
eureka.client.service-url.defaultZone=http://localhost:7002/eureka/
eureka高可用配置服务相互注册
server.port=7001
#设置服务注册中心的URL,用于client和server端交流
eureka.client.service-url.defaultZone=http://euk2.top:7002/eureka/
#设置服务名
spring.application.name=eureka-server
server.port=7002
#设置服务注册中心的URL,用于client和server端交流
eureka.client.service-url.defaultZone=http://euk1.top:7001/eureka/
#设置服务名
spring.application.name=eureka-server
@Autowired
DiscoveryClient client; // 抽象
@Autowired
EurekaClient client2; // Eureka
List services = client.getServices();//获取所有服务实例id。
List instances = client.getInstances("provider");//通过服务id查询服务实例信息列表。
@Bean
// 开启负载均衡
@LoadBalanced
RestTemplate restTemplate() {
return new RestTemplate();
}
get 请求处理
@RestController
@RequestMapping("/demo")
public class DemoController {
@Autowired
RestTemplate restTemplate;
@GetMapping("getHi")
public String getHi(){
String url = "http://PROVIDER/demo/getHi";
String resp = restTemplate.getForObject(url, String.class);
return resp;
}
}
post 请求处理
exchange可以自定义http请求的头信息,同时保护get和post方法
拦截器
需要实现ClientHttpRequestInterceptor接口
public class LoggingClientHttpRequestInterceptor implements ClientHttpRequestInterceptor {
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
throws IOException {
System.out.println("拦截啦!!!");
System.out.println(request.getURI());
ClientHttpResponse response = execution.execute(request, body);
System.out.println(response.getHeaders());
return response;
}
添加到resttemplate中
@Bean
@LoadBalanced
RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
restTemplate.getInterceptors().add(new LoggingClientHttpRequestInterceptor());
return restTemplate;
}
pom
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-ribbonartifactId>
dependency>
切换负载均衡策略
注解方式
@Bean
public IRule myRule(){
//return new RoundRobinRule();
//return new RandomRule();
return new RetryRule();
配置文件
针对服务定ribbon策略:
provider.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule
给所有服务定ribbon策略:
ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule
属性配置方式优先级高于Java代码。
Ribbon脱离Eureka
设置 请求的网络地址列表。
Ribbon可以和服务注册中心Eureka一起工作,从服务注册中心获取服务端的地址信息,也可以在配置文件中使用listOfServers字段来设置服务端地址。
ribbon.eureka.enabled=false
ribbon.listOfServers=localhost:80,localhost:81
用法:
@RestController
@RequestMapping("/demo")
public class DemoController {
@Autowired
LoadBalancerClient lb;
@Autowired
RestTemplate restTemplate;
@GetMapping("getHi")
public String getHi(){
// ribbon 完成客户端的负载均衡,过滤掉down了的节点
ServiceInstance provider = lb.choose("PROVIDER");
String url ="http://" + provider.getHost() +":"+ provider.getPort() + "/demo/getHi";
return restTemplate.getForObject(url, String.class);
}
}
手动实现,其实也是它的原理,做事的方法
手写客户端负载均衡
1、知道自己的请求目的地(虚拟主机名,默认是spring.application.name)
2、获取所有服务端地址列表(也就是注册表)。
3、选出一个地址,找到虚拟主机名对应的ip、port(将虚拟主机名 对应到 ip和port上)。
4、发起实际请求(最朴素的请求)。
负载均衡算法
默认实现:
ZoneAvoidanceRule
(区域权衡策略):复合判断Server所在区域的性能和Server的可用性,轮询选择服务器。
其他规则:
BestAvailableRule
(最低并发策略):会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务。逐个找服务,如果断路器打开,则忽略。
RoundRobinRule
(轮询策略):以简单轮询选择一个服务器。按顺序循环选择一个server。
RandomRule
(随机策略):随机选择一个服务器。
AvailabilityFilteringRule
(可用过滤策略):会先过滤掉多次访问故障而处于断路器跳闸状态的服务和过滤并发的连接数量超过阀值得服务,然后对剩余的服务列表安装轮询策略进行访问。
WeightedResponseTimeRule
(响应时间加权策略):据平均响应时间计算所有的服务的权重,响应时间越快服务权重越大,容易被选中的概率就越高。刚启动时,如果统计信息不中,则使用RoundRobinRule(轮询)策略,等统计的信息足够了会自动的切换到WeightedResponseTimeRule。响应时间长,权重低,被选择的概率低。反之,同样道理。此策略综合了各种因素(网络,磁盘,IO等),这些因素直接影响响应时间。
RetryRule
(重试策略):先按照RoundRobinRule(轮询)的策略获取服务,如果获取的服务失败则在指定的时间会进行重试,进行获取可用的服务。如多次获取某个服务失败,就不会再次获取该服务。主要是在一个时间段内,如果选择一个服务不成功,就继续找可用的服务,直到超时。
pom
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
启动类
@EnableFeignClients
FeignClieng
@FeignClient(name="provider",configuration = FeignConfig.class)
public interface ProviderClient extends ApiDemo {
}
服务提供方接口Controller,继承到FeignClient就省的自己写。
@RequestMapping("/demo")
public interface ApiDemo {
@GetMapping("/getHi")
String getHi();
}
远程调用
@Autowired
ProviderClient providerClient;
@GetMapping("getHiFeign")
public String getHiFeign(){
return providerClient.getHi();
}
feign的配置类( 1.自定义配置类、2.增加拦截器)
RequestInterceptor
,用于header中增加设置参数
通用配置
@Configuration
public class FeignConfig implements RequestInterceptor {
private String iamKey="123456";
@Override
public void apply(RequestTemplate requestTemplate) {
requestTemplate.header("Content-Type","application/json");
requestTemplate.header("X-Gaia-Api-Key",iamKey);
}
}
配置类不加@Configuration,在配置文件里配置哪个服务用哪个配置类provider
是服务名,不指定就写defult
@FeignClient(name = "provider")
feign:
client:
config:
provider:
request-interceptors:
- com.msb.config.FeignConfig
feign:
client:
config:
default:
connect-timeout: 5000
read-timeout: 5000
logger-level: full
feign:
client:
config:
provider:
connect-timeout: 5000
read-timeout: 5000
logger-level: full
feign:
client:
default-to-properties: false
原理
压缩
#服务端开启压缩
server.compression.enabled=true
#配置请求GZIP压缩
feign.compression.request.enabled=true
#配置响应GZIP压缩
feign.compression.response.enabled=true
#单位是B
feign.compression.request.min-request-size=100
开启日志
logging.level.com.mashibing.UserConsumer:debug
@Configuration
public class FeiginConfig {
@Bean
Logger.Level logLevel(){
return Logger.Level.BASIC;
}
}
超时
#连接超时时间(ms)
ribbon.ConnectTimeout=1000
#业务逻辑超时时间(ms)
ribbon.ReadTimeout=6000
重试
使用ribbon重试机制,请求失败后,每个6秒会重新尝试
#同一台实例最大重试次数,不包括首次调用
ribbon.MaxAutoRetries=1
#重试负载均衡其他的实例最大重试次数,不包括首次调用
ribbon.MaxAutoRetriesNextServer=1
#是否所有操作都重试
ribbon.OkToRetryOnAllOperations=false
Eureka 健康检查
由于server和client通过心跳保持 服务状态,而只有状态为UP的服务才能被访问。看eureka界面中的status。
比如心跳一直正常,服务一直UP,但是此服务DB连不上了,无法正常提供服务。
开启手动控制
在client端配置:将自己真正的健康状态传播到server。
eureka:
client:
healthcheck:
enabled: true
Client端配置Actuator
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
@Service
public class HealthStatusService implements HealthIndicator{
private Boolean status = true;
public void setStatus(Boolean status) {
this.status = status;
}
@Override
public Health health() {
// TODO Auto-generated method stub
if(status)
return new Health.Builder().up().build();
return new Health.Builder().down().build();
}
public String getStatus() {
// TODO Auto-generated method stub
return this.status.toString();
}
测试用的Controller
@GetMapping("/health")
public String health(@RequestParam("status") Boolean status) {
healthStatusSrv.setStatus(status);
return healthStatusSrv.getStatus();
}
开启所有端点
#开启所有端点
management.endpoints.web.exposure.include=*
api端点功能
management.endpoint.shutdown.enabled=true
metrics/jvm.memory.max
pom
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-hystrixartifactId>
dependency>
public class HystrixTest extends HystrixCommand {
public static void main(String[] args) {
String result="";
Future<String> spring = new HystrixTest(HystrixCommandGroupKey.Factory.asKey("spring")).queue();
try {
result = spring.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
System.out.println("程序结果:"+result);
}
protected HystrixTest(HystrixCommandGroupKey key) {
super(key);
// TODO Auto-generated constructor stub
}
@Override
protected Object run() throws Exception {
System.out.println("执行逻辑");
int i = 1/0;
return "ok";
}
@Override
protected Object getFallback() {
// TODO Auto-generated method stub
return "getFallbackgetFallback";
}
}
启动类
@EnableCircuitBreaker
@HystrixCommand(fallbackMethod = "back")
public String alive() {
// 自动处理URL
RestTemplate restTemplate = new RestTemplate();
String url ="http://user-provider/User/alive";
String object = restTemplate.getForObject(url, String.class);
return object;
}
public String back() {
return "请求失败~bbb...";
}
启动类
@EnableHystrix
配置文件开启
feign.hystrix.enabled=true
FeignClient添加fallback执行类
fallback类
FallbackFactory类
@Component
public class ProError implements FallbackFactory<ProviderClient> {
@Override
public ProviderClient create(Throwable throwable) {
return new ProviderClient() {
@Override
public String getHi() {
throwable.printStackTrace();
if(throwable instanceof FeignException.InternalServerError) {
System.out.println("InternalServerError");
return "远程服务报错";
}else if(throwable instanceof RuntimeException) {
System.out.println("RuntimeException");
return "远程服务链接超时";
}else {
return "都算不上";
}
}
};
}
}
默认情况下hystrix使用线程池控制请求隔离
线程池隔离技术,是用 Hystrix 自己的线程去执行调用;而信号量隔离技术,是直接让 tomcat 线程去调用依赖服务。信号量隔离,只是一道关卡,信号量有多少,就允许多少个 tomcat 线程通过它,然后去执行。
信号量隔离主要维护的是Tomcat的线程,不需要内部线程池,更加轻量级。
配置
hystrix.command.default.execution.isolation.strategy 隔离策略,默认是Thread, 可选Thread|Semaphore
thread 通过线程数量来限制并发请求数,可以提供额外的保护,但有一定的延迟。一般用于网络调用
semaphore 通过semaphore count来限制并发请求数,适用于无网络的高并发请求
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds 命令执行超时时间,默认1000ms
hystrix.command.default.execution.timeout.enabled 执行是否启用超时,默认启用true
hystrix.command.default.execution.isolation.thread.interruptOnTimeout 发生超时是是否中断,默认true
hystrix.command.default.execution.isolation.semaphore.maxConcurrentRequests 最大并发请求数,默认10,该参数当使用ExecutionIsolationStrategy.SEMAPHORE策略时才有效。如果达到最大并发请求数,请求会被拒绝。理论上选择semaphore size的原则和选择thread size一致,但选用semaphore时每次执行的单元要比较小且执行速度快(ms级别),否则的话应该用thread。
semaphore应该占整个容器(tomcat)的线程池的一小部分。
Feign下配置
hystrix.command.default.execution.isolation.strategy=SEMAPHORE
pom
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboardartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
启动类
@EnableHystrixDashboard
图形化
http://localhost:90/hystrix
健康上报
http://localhost:90/actuator/hystrix.stream
pom
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-zuulartifactId>
dependency>
启动类
@EnableZuulProxy
访问(网关会将服务名转换成具体服务的ip和端口,实际进行访问)
http://euk1.top:1000/consumer/demo1/getHiFeign
负载均衡(默认是轮询)
consumer.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule
路由端点
调试的时候,看网关请求的地址,以及 映射是否正确。网关请求有误时,可以通过此处排查错误。
management.endpoints.web.exposure.include=*
management.endpoint.health.show-details=always
management.endpoint.health.enabled=true
management.endpoint.routes.enabled=true
配置指定微服务的访问路径
通过服务名配置(虚拟主机名)
http://euk1.top:1000/consumer/demo1/getHiFeign
http://euk1.top:1000/consumer_v1/demo1/getHiFeign
zuul.routes.consumer=/consumer_v1/**
自定义映射
zuul.routes.baidu.path=/baidu/**
zuul.routes.baidu.url=http://baidu.com
自定义下的负载均衡
zuul.routes.user-provider.path=/user-provider/**
zuul.routes.user-provider.service-id=cuid
cuid.ribbon.listOfServers=euk1.top:8001,euk1.top:8002
ribbon.eureka.enabled=false
忽略微服务
zuul.ignored-services=consumer
前缀
zuul.prefix=/api/v1
zuul.strip-prefix=false
Sleuth是Spring cloud的分布式跟踪解决方案。
pom
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-sleuthartifactId>
<version>2.2.6.RELEASEversion>
dependency>
上面拍错看日志,很原始。刀耕火种,加入利器 zipkin。
zipkin是twitter开源的分布式跟踪系统。
原理收集系统的时序数据,从而追踪微服务架构中系统延时等问题。还有一个友好的界面。
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-zipkinartifactId>
dependency>
spring:
#zipkin
zipkin:
base-url: http://localhost:9411/
#采样比例1
sleuth:
sampler:
rate: 1
jar包下载:wget ‘https://search.maven.org/remote_content?g=io.zipkin.java&a=zipkin-server&v=LATEST&c=exec’
curl -sSL https://zipkin.io/quickstart.sh | bash -s
我放到了 目录:C:\github\online-taxi-demo 下面。
java -jar zipkin.jar
或者docker:
docker run -d -p 9411:9411 openzipkin/zipkin
pom
<properties>
<spring-boot-admin.version>2.2.1spring-boot-admin.version>
properties>
<dependency>
<groupId>de.codecentricgroupId>
<artifactId>spring-boot-admin-starter-serverartifactId>
<version>${spring-boot-admin.version}version>
dependency>
<dependency>
<groupId>de.codecentricgroupId>
<artifactId>spring-boot-admin-server-uiartifactId>
<version>${spring-boot-admin.version}version>
dependency>
配置
server.port=1100
#设置服务注册中心的URL,用于client和server端交流
eureka.client.service-url.defaultZone=http://euk2.top:7002/eureka/,http://euk1.top:7001/eureka/
spring.application.name=admin
management.endpoints.web.exposure.include=*
management.endpoint.health.show-details=always
management.endpoint.health.enabled=true
management.endpoint.routes.enabled=true
启动类
@EnableAdminServer
pom
<dependency>
<groupId>de.codecentricgroupId>
<artifactId>spring-boot-admin-starter-clientartifactId>
<version>2.2.1version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
配置
management.endpoints.web.exposure.include=*
management.endpoint.health.show-details=always
spring.boot.admin.client.url=http://localhost:1100
pom
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-mailartifactId>
dependency>
配置
# 邮件设置
spring.mail.host=smtp.qq.com
spring.mail.username=QQ号码
spring.mail.password=pbwyfawjefspbdhd1
spring.mail.properties.mail.smpt.auth=true
spring.mail.properties.mail.smpt.starttls.enable=true
spring.mail.properties.mail.smpt.starttls.required=true
#收件邮箱
spring.boot.admin.notify.mail.to=QQ号码@qq.com
# 发件邮箱
spring.boot.admin.notify.mail.from=QQ号码@qq.com
pom
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>1.2.68version>
dependency>
配置
# spring cloud access&secret config
# 可以访问如下地址查看: https://usercenter.console.aliyun.com/#/manage/ak
spring.cloud.alicloud.access-key=LTAIEW7ORp2hYTBB1
spring.cloud.alicloud.secret-key=TSqXlzrivoVYdD7CH9HVmjKwbtC9Xa1
启动类
@Bean
public DingDingNotifier dingDingNotifier(InstanceRepository repository) {
return new DingDingNotifier(repository);
}
DingDingNotifier
public class DingDingNotifier extends AbstractStatusChangeNotifier {
public DingDingNotifier(InstanceRepository repository) {
super(repository);
}
@Override
protected Mono<Void> doNotify(InstanceEvent event, Instance instance) {
String serviceName = instance.getRegistration().getName();
String serviceUrl = instance.getRegistration().getServiceUrl();
String status = instance.getStatusInfo().getStatus();
Map<String, Object> details = instance.getStatusInfo().getDetails();
StringBuilder str = new StringBuilder();
str.append("系统警告 : 【" + serviceName + "】");
str.append("【服务地址】" + serviceUrl);
str.append("【状态】" + status);
str.append("【详情】" + JSONObject.toJSONString(details));
return Mono.fromRunnable(() -> {
DingDingMessageUtil.sendTextMessage(str.toString());
});
}
}
DingDingMessageUtil
public class DingDingMessageUtil {
// 从钉钉群获取的
public static String access_token = "55420aefab13c4cf75b3ad144a0efa71ba406ba0037e2619fb99ad2ca5e2c452";
public static void sendTextMessage(String msg) {
//https://oapi.dingtalk.com/robot/send?access_token=55420aefab13c4cf75b3ad144a0efa71ba406ba0037e2619fb99ad2ca5e2c452
try {
Message message = new Message();
message.setMsgtype("text");
message.setText(new MessageInfo(msg));
URL url = new URL("https://oapi.dingtalk.com/robot/send?access_token=" + access_token);
// 建立 http 连接
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setUseCaches(false);
conn.setRequestMethod("POST");
conn.setRequestProperty("Charset", "UTF-8");
conn.setRequestProperty("Content-Type", "application/Json; charset=UTF-8");
conn.connect();
OutputStream out = conn.getOutputStream();
String textMessage = JSONObject.toJSONString(message);
byte[] data = textMessage.getBytes();
out.write(data);
out.flush();
out.close();
InputStream in = conn.getInputStream();
byte[] data1 = new byte[in.available()];
in.read(data1);
System.out.println(new String(data1));
} catch (Exception e) {
e.printStackTrace();
}
}
}
Message
public class Message {
private String msgtype;
private MessageInfo text;
public String getMsgtype() {
return msgtype;
}
public void setMsgtype(String msgtype) {
this.msgtype = msgtype;
}
public MessageInfo getText() {
return text;
}
public void setText(MessageInfo text) {
this.text = text;
}
}
MessageInfo
public class MessageInfo {
private String content;
public MessageInfo(String content) {
this.content = content;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
pom
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-config-serverartifactId>
dependency>
配置文件
server.port=1141
# git地址
spring.cloud.config.server.git.uri=https://gitee.com/a84/config-server.git
# git分支
spring.cloud.config.label=master
#设置服务注册中心的URL,用于client和server端交流
eureka.client.service-url.defaultZone=http://euk2.top:7002/eureka/,http://euk1.top:7001/eureka/
spring.application.name=config-server
下面简单说一下,因为我们读取的配置文件名字叫做【provider-dev.properties】,所以spring.application.name对应【provider】;属性【profile】对应【dev】;然后就是spring.cloud.config.discovery.service-id对应的是注册在Eureka上的config Server的名字
启动类
@EnableEurekaClient
@EnableConfigServer
启动测试拉取
不写默认master
http://host:port/lable/fileName
http://localhost:1141/master/config-client-dev.properties
匹配规则
正确配置后能读到来自git的配置文件
获取配置规则:根据前缀匹配
/{name}-{profiles}.properties
/{name}-{profiles}.yml
/{name}-{profiles}.json
/{label}/{name}-{profiles}.yml
name 服务名称
profile 环境名称,开发、测试、生产:dev qa prd
lable 仓库分支、默认master分支
匹配原则:从前缀开始。
pom
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-config-clientartifactId>
dependency>
配置文件
#设置服务注册中心的URL,用于client和server端交流
eureka.client.service-url.defaultZone=http://euk2.top:7002/eureka/,http://euk1.top:7001/eureka/
spring.application.name=consumer
#直接URL方式查找配置中心
#spring.cloud.config.uri=http://localhost:1141/
#通过注册中心查找
spring.cloud.config.discovery.enabled=true
spring.cloud.config.discovery.service-id=config-server
spring.cloud.config.profile=dev
spring.cloud.config.label=master
注意:
config-client端服务名
,配置文件name
启动日志
加载了 master分支的consumer-dev配置文件
手动配置热更新
自动配置热更新(Spring Cloud Bus + RabbitMQ)
pom(server + client)
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
配置文件(server + client)
spring.rabbitmq.host=81.70.111.111
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
测试
启动两个微服务
bus-refresh
refresh
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
spring.rabbitmq.host=81.70.111.111
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest