SpringCloud
微服务定义
- 是一种架构风格
- 一系列微小的服务共同组成
- 每个服务跑在自己的进程里
- 每个服务为独立的业务开发
- 独立部署
- 分布式的管理
架构形态
单体架构
特点:
- 打包成一个war包,没有外部依赖
- 共用一个db
- 容易测试
- 容易部署
- 开发效率低
- 代码维护困难
-
部署不够灵活
- 构建时间特别长
- 稳定性不高
- 扩展性不够,不能满足高并发需求
基于ajax的前后端分离
分布式架构
定义:支持应用程序和服务的开发,可以利用物理架构和多个自制的处理元素(多节点),不共享主内存,但通过网络发送消息
简单的微服务
基础框架组件
服务注册发现
服务网关
- 连接内外大门
- 屏蔽后台细节,让用户无感知
- 路由功能,外部请求反向路由到内部某个服务
-
网关功能,控制流量,监控和日志
- 用户认证
- 授权
- 服务网关
前端服务(边缘服务)
聚合
把两个接口聚合在一起返回出去
裁剪
通过不同需求返回不同数据,pc和手机端淘宝返回详情数据不一致
后端通用服务(中间层服务)
阿里系
Dubbo
Zookeeper
Spring MVC or SpringBoot
Spring Cloud
定义
- 是一个开发工具集
- 利用spring boot的开发遍历
- 基于对netflix 开源组件的进一步封装
- 简化了分布式开发
来源
基于Netflix Eureka做了二次封装
文档
http://projects.spring.io/spr...
版本查看
Eureka Server(注册中心)
-Dserver.port=
pom文件
org.springframework.cloud
spring-cloud-starter-netflix-eureka-server
配置方式
eureka:
client:
service-url:
defaultZone: http://localhost:8080/eureka/
# 不把自己注册到注册中心
register-with-eureka: false
# 设置应用名
spring:
application:
name: eureka
打包
mvn clean package install
Eureka Client(服务注册)
pom文件
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
yml配置方式
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
# 不把自己注册到注册中心
register-with-eureka: false
#关闭心跳监测
server:
enable-self-preservation: false
# 设置应用名
spring:
application:
name: eureka
server:
port: 8761
开启注解
@EnableDiscoveryClient
和@EnableEurekaClient
区别
如果注册中心是eureka那么推荐使用EnableEurekaClient`
如果是其他注册中心那么推荐使用Enable
Eureka的高可用
两个eureka都能看到client的信息
如果一个client挂掉了那么两个eureka都没有信息
解决:
多台
分布式中为什么需要服务发现
a和b沟通完全通过注册中心
服务端发现
- Nginx
- Zookeeper
- Kubernetes
客户端发现
- Eureka
微服务的特点
- 异构
- 不同类型的数据库
- Spring Cloud通过rest方式实现服务注册
- Node.js的eureka-js-client
服务拆分方法
- 分清楚起点和终点
- 了解现有架构是否支持微服务架构
- 架构是不断引进的
不适合微服务的系统
- 系统中包含很多强事物场景的
- 业务相对稳定,迭代周期长
- 访问压力不大,可用性要求不高
康威定律
任何组织在设计一套系统时,所交互的设计方案在结构上都与该组织的沟通结构保持一直
微服务特点
- 一系列微小的服务共同组成
- 单独部署,跑在自己的进程里
- 每个服务为独立的业务开发
- 分布式的管理
扩展立方模型
- X轴 水平复制
-
Y轴功能解耦
- 单一职责,每个服务负责单一功能
- 相关功能聚集在一个服务内
-
关注点分离
- 职责分离
-
通用性分离
- 基础组件划分
- 消息服务
- 用户服务
- 公共组件拆分
-
粒度分离
- 不是越小越好
- Z轴 数据分区
点餐业务拆分
拆分服务
服务拆分方法论
- 每个微服务都有单独的数据存储
-
依据服务特点选择不同结构的数据库类型
- 搜索->elasticsearch
- 订单类-》mysql
-
难点在难以确定边界
- 针对边界设置api
- 依据边界权衡数据冗余
应用通信
调用的三种方式
-
直接使用RestTemplate
String s = restTemplate.getForObject("http://localhost:8083/hello", String.class);
-
使用LoadbalanceClient
ServiceInstance serviceInstance = loadBalancerClient.choose("PRODUCT"); String url = String.format("http://%s:%s", serviceInstance.getHost(), serviceInstance.getPort() + "/hello"); String result = restTemplate.getForObject(url, String.class);
-
使用注解
@Bean @LoadBalanced public RestTemplate restTemplate() { return new RestTemplate(); }
客户端负载均衡器(Ribbon)
- RestTemplate
- Feign
- Zuul
发现方式
- 服务发现
- 服务选择规则
- 服务监听
主要组件
- ServerList
- IRule
- ServerListFilter
源码解析
- 用来获取所有列表
-
轮循策略默认是roundRobinRule
修改默认轮询策略
-
http://cloud.spring.io/spring...
users: ribbon: NFLoadBalancerRuleClassName: com.netflix.loadbalancer.WeightedResponseTimeRule
- 轮循策略
-
Feign使用(内部也使用了rabbion做负载均衡)
-
Pom文件
org.springframework.cloud spring-cloud-starter-feign
- 启动主类增加注解
@FeignClient
-
编写接口
@FeignClient("product") public interface FeignConfig { @GetMapping("hello") String hello(); }
-
注入和调用
@Autowired private FeignConfig feignConfig; @RequestMapping("/feign") public String feign() { String result = feignConfig.hello(); return result; }
多模块拆分服务
问题
- 商品和订单dto重复定义
- 同一个对象多次重复定义
- 订单服务里面不应该知道商品服务的uri
模块拆分
-
product-server
- 业务逻辑
-
product-client
- 对外暴露的接口
-
Product-common
- 公用的对象
-
依赖关系
打包命令
mvn -Dmaven.test.skip=true -U clean install
同步还是异步
-
- 消费者通过消息中间件进行解耦
- 用户调用短信服务,积分服务,其他服务,服务耦合过大,用户登录成功需要多个服务同步响应后才告诉成功
-
-
商品服务库存变化发布消息,订单服务订阅消息,比如商品信息
- 订单不需要查询商品服务而是查询自己服务中的信息
- 保证数据最终一致性,只需要订阅对应服务就能保证
-
-
常见消息队列
- RabbitMQ
- Kafka
- ActiveMQ
RabbitMQ安装
5672->默认RabbitMQServer端口
15672->RabbitMQ管理页面端口,页面只需要配置这个
docker run -d --hostname my-rabbit -p 5672:5672 -p 15672:15672 rabbitmq:3.7.3-management
默认密码:guest
Docker和DevOps
- docker能够解决不同环境下应用程序都能运行
- 轻量
- 进程隔离和管理
- 可复用,版本化(tag机制)
- 微服务架构师核心,devops和docker是手段
统一配置中心(Spring Cloud Config)
原因
- 配置方式不方便维护
- 配置内容与权限(针对线上)
- 每更新一个配置都要重启项目
ConfigServer使用
configServer从远端git拉下配置放到本地git如果远端不可用使用本地的
- pom
org.springframework.cloud spring-cloud-config-server org.springframework.cloud spring-cloud-starter-netflix-eureka-client
-
开启注解
@EnableDiscoveryClient @EnableConfigServer
- 没有配置对应的gituri
-
在git上新建项目
- 在配置中填写对应git uri,username,password
-
访问对应配置文件
- .properties展示为properties ,.yml为yml
-
/{label}/{name}-{profiles}
- label->分支(不写默认master)
- name->文件名
- profiles->环境
-
设置配置文件基础目录
spring.cloud.config.server.git.basedir
ConfigClient使用
导入依赖
org.springframework.cloud
spring-cloud-config-client
更改配置
spring:
application:
name: order
cloud:
config:
discovery:
enabled: true
service-id: config
profile: dev
修改application.yml
修改为bootstrap.yml这样会优先启动
注意
配置中心会读取拼接order.yml+order-xx.yml的内容
Spring Cloud Bus
通过git hook访问配置文件变更,同步信息到消息队列,(/bus/refresh)
Config-server 通过消息队列同步到其他服务
使用
更新版本
org.springframework.boot
spring-boot-starter-parent
2.0.0.BUILD-SNAPSHOT
UTF-8
UTF-8
1.8
Finchley.BUILD-SNAPSHOT
导入jar包
org.springframework.cloud
spring-cloud-starter-bus-amqp
2.0.0.RELEASE
yml开放端口用来刷新对应变更
management:
endpoints:
web:
exposure:
include: bus-refresh
访问端口刷新配置
curl -v -X POST "http://localhost:8991/actuator/bus-refresh"
需要变更的类上面加上注解@RefreshScope
@RestController
@RequestMapping("/env")
@RefreshScope
public class EnvController {
@Value("${env}")
public String env;
@GetMapping("/profile")
public String getenv() {
return env;
}
}
通过 git HOOK自动访问地址
有bug
异步和消息
定义
-
异步:客户端请求不会阻塞进程,服务端的响应可以是非及时的
- 通知
- 请求异步响应
- 客户端不会阻塞
-
通过消息实现一对多
- 客户端发送请求消息等待服务端响应
MQ
应用场景
-
异步处理
- 用户注册后需要发短信和加积分
-
流量削锋
- 秒杀场景,丢弃请求,控制活动人数
-
日志处理(kafka)
- 通过日志采集定时写入队列
-
应用解耦
- 用户下单后需要调用商品服务,使用mq用户下单后把消息写入商品服务,商品使用拉或者推的方式,订单服务写入订单后就可以不关注后续流程了
使用
导入依赖
org.springframework.boot
spring-boot-starter-amqp
yml配置
spring:
application:
name: order
cloud:
config:
discovery:
enabled: true
service-id: config
profile: test
rabbitmq:
host: localhost
port: 5672
username: guest
guest: guest
发送方式
@Autowired
private AmqpTemplate amqpTemplate;
@Test
public void send() {
amqpTemplate.convertAndSend("myQueue", "now time:" + System.currentTimeMillis());
}
接收方式
第一种(需要手动创建队列)
@Slf4j
@Component
public class MqReceiver {
@RabbitListener(queues = "myQueue")
public void process(String message) {
log.info("MqReceiver:{}", message);
}
}
第二种 自动创建队列
/**
* 自动创建队列
*
* @param message
*/
@RabbitListener(queuesToDeclare = @Queue("myQueue2"))
public void autoCreateQueue(String message) {
log.info("autoCreateQueue:{}", message);
}
第三种 Exchange和Queue绑定
@RabbitListener(bindings = {@QueueBinding(
value = @Queue("myQueueExchange"),
exchange = @Exchange("myExchange")
)})
public void exchange(String message) {
log.info("myExchange:{}", message);
}
消息分组使用第三种方式
@RabbitListener(bindings = @QueueBinding(
exchange = @Exchange("myOrder"),
key = "computer",
value = @Queue("computerOrder")
))
public void computerOrder(String message) {
log.info("computerOrder:{}", message);
}
@RabbitListener(bindings = @QueueBinding(
exchange = @Exchange("myOrder"),
key = "fruit",
value = @Queue("fruitOrder")
))
public void fruitOrder(String message) {
log.info("fruitOrder:{}", message);
}
@Test
public void sendToProduct() {
amqpTemplate.convertAndSend("myOrder", "computer", "now time:" + System.currentTimeMillis());
}
Spring Cloud Stream
为微服务应用构建消息能力的应用,对于消息中间件的封装,代码对于中间件的无感知,但是只支持rabbitMQ 和Kafka
使用
导入pom
org.springframework.cloud
spring-cloud-starter-stream-rabbit
定义接口
public interface StreamClient {
@Input("myMessage")
SubscribableChannel input();
@Output("myMessage")
MessageChannel output();
}
接受
@Component
@EnableBinding(StreamClient.class)
@Slf4j
public class StreamReceiver {
@StreamListener("myMessage")
public void process(Object message) {
log.info("StreamReceiver:{}",message);
}
}
发送
@RestController
public class SendMessageController {
@Autowired
private StreamClient streamClient;
@GetMapping("/sendMessage")
public void process() {
String message = "now " + new Date();
streamClient.output().send(MessageBuilder.withPayload(message).build());
}
}
参考:https://coding.imooc.com/learn/list/187.html