1 微服务的着眼点及解决方案
Wed, Nov 7, 2018
0x0 微服务带来的问题
服务之间的通讯问题?
- 单体架构中需要通讯的情况少见,而微服务之间的相互调用非常频繁
微服务如何发现彼此?
- 单体架构使用Dubbo做服务发现:服务消费者需要对服务的提供者进行发现。
- 发现的原理是通过KV存储
- 有一个中间人,提供者把自己的信息告诉中间人,消费者去中间人那里拿提供者的地址
- 微服务有各种语言,如何彼此发现?
微服务怎样部署?更新?扩容?
- 自动化流程?Jekins部署到预发环境,没问题部署到生产环境?
- 微服务少的情况下可以这么做,但是一般很难这么做,因为需要同时上线的微服务器可能非常多,无法手动搞
0x1 微服务间如何通讯
通讯:socket、tcp/ip、http...
两个角度考虑通讯:
- 通讯模式
- 通讯协议
通讯模式角度
通讯协议角度
REST API
特点:
- 请求方式描述动作类
- REST并不是适合于所有业务场景
RPC
MQ - 消息队列
发布订阅的模式,可以使用MQ的方式实现
RPC专项
选择RPC框架
- I/O、线程调度模型
- 同步?非阻塞的异步?
- 长连接?短连接?
- 单线程?多线程?线程调度算法?
- 序列化方式(效率影响了RPC通讯效率)
- 可读的
- XML
- JSON
- 二进制
- pb
- 可读的
- 多语言支持
- 服务治理
- 有木有服务发现?
- 服务监控?
流行的RPC框架
- Dubbo/Dubbox
- 阿里/当当
- 蓝色:服务启动的时候做的事
- 实现:同步调用,虚线:异步调用
- 特点:
- 只支持Java
- 有完善的服务治理
- Motan
- 新浪微博
- 特点:
- 支持Java
- Thrift
- facebook/Apache
- 最下面:定义一个API的文件,然后通过Thrift生成定义各种语言代码
- 生成之后,服务的提供者和消费者需要引用生成的API
- 服务端通过引用,做实现功能的实现,消费者通过API的存根直接调用提供者
- 特点:
- 跨语言
- CS模式
- 通过代码生成工具,讲接口定义的文件生成服务器端和客户端的代码(服务端和客户端的代码可以是不同语言的),进而实现服务端和客户端的跨语言支持
- 序列化:支持很多种
- 二进制模式
- 压缩格式
- 线上使用,提升性能
- Json模式
- Debug模式
- 开发中使用
- 通讯模式(多种):
- 阻塞IO
- NIO
- 专门传输文件的传输方式
- 线程模型(多种):
- 单线程模式
- 线程池模式
- 多线程使用非阻塞IO模式
- 没有服务治理相关东西(没有服务发现和监控)
- 消费者只能通过提供者的IP:PORT访问
- 需要自己实现服务发现
- Grpc
- 说明:和Thrift类似,需要先写API的定义文件,文件格式是protoBuffer的,然后去生成各语言接口,然后实现跨语言调用
- 序列化:pb(高效的序列化方式)
- 通讯协议:没用socket,基于http/2 - 主要面向移动端
- 编码和传输效率低于socket模式
RPC框架对比
0x2 服务发现、部署更新扩容
服务发现
所有服务对外的形式都是IP:PORT形式
服务发现的本质:让调用者知道提供者的IP和PORT
传统服务「发现」
虚线:主机,上面部署了两个服务
- 客户端通过DNS解析到Nginx
- Nginx中配置了两台服务器的IP和PORT
- Nginx有一个负载均衡策略,如轮询,会依次访问到右边的两个服务
传统的服务「发现」,其实没有发现什么东西:
- 所有服务提供者已经写死了
- 配置文件的维护和更新需要运维同学「手动操作」
服务发现
客户端的服务发现
微服务启动之后,会把IP和PORT告诉注册中心
客户端从注册中心查需要访问的服务,然后连这个服务
服务端的服务发现
客户端只固定访问一个服务器,这台服务器来做客户端消息的转发、服务内部的发现、负载均衡选择服务器之类的事情
服务的部署、更新、扩容
传统流程
部署流程
代码写好,内网测试通过准备上线→跟运维交涉找到一台空闲的服务器用来部署服务→运维把应用cp到服务器上(Jekins、脚本、其他自动化方式)-(web服务)→ cp一个Tomcat,给服务分配一个端口号(查询端口列表,找一个没有被占用的端口)→ 给域名做DNS解析,解析到对应的Nginx→ Nginx里配置反向代理,指向刚配置的Tomcat
更新流程
单服务器情况:替换旧代码 & 重启服务
双服务器(高可用)情况:线下一台服务器→ 代码更新重启验证没问题→ 上线→ 重复部署到另一台服务器上
扩容
和部署差不多
微服务流程
特点:服务数量巨多,更新上线非常频繁,传统流程运维同学无法接受
解决方案:服务编排
服务编排
包括服务发现、部署、更新、扩容功能,目的是「简化」
服务编排工具
Mesos、Docker Swarm、Kubernetes
0x3 SpringBoot & SpringCloud
Java世界里和微服务密切相关的东东
参考资料:Spring Framework
SpringBoot
目的:化繁为简
核心功能:
- 独立运行
- 以前运行Spring服务的方式
- 一个web服务器,如Tomcat
- 把代码发布到服务器的指定位置
- 启动服务
- SpringBoot的运行方式
- 以一个Java命令的形式运行服务
- java -jar xxxxx.jar
- 以一个Java命令的形式运行服务
- 以前运行Spring服务的方式
- 内嵌web服务器
- 内嵌了Tomcat
- 简化配置
- 提供准生产的应用监控
与微服务的关系
SpringBoot是Java开发微服务的润滑剂,让开发微服务的效率变高了。不是开发微服务的必须
SpringBoot的简化思路,贴合了微服务的理念
SpringCloud
目的:简化Java的分布式系统的通用性问题
- 多个服务的负载均衡
- 服务之间的调用
- 事务管理
- 统一的注册管理
理解:
- 一系列框架的集合
- 简化Java的分布式系统开发 - 利用了SpringBoot
- 用SpringBoot对一些轮子做的封装
与微服务的关系
专门来做微服务的,是Java的微服务解决方案
- 侧重功能开发,让开发更快
- 没提供资源的管理、部署等
核心组件
Eureka:服务发现
发现模式:客户端发现
- Eureka Server:是一个注册中心,运行多个实例可以避免「单点」的问题,保证高可用
- Application Service:注册到Eureka且保持心跳,心跳停止会认为其不可用
- Application Client:通过Eureka Server查询获取服务的注册信息(IP PORT)
- Client和Service自己通信
Ribbon:客户端侧的负载均衡
- Edge Service:负责把各业务服务提供的数据串联起来,统一提供给客户端的服务(如景点信息、门票信息、景点评论等)
- 特点:
- 整合后端服务数据
- 离客户端最近
- 问题:
- Edge Service和API Gateway的区别?
- 答案见文章下方Zuul部分
- Edge Service和API Gateway的区别?
- 特点:
- 请求通过ELB来到API Service
- API Service 通过Eureka的服务发现找到服务列表
- 根据RibbonClient提供的负载均衡策略,选择访问指定的服务
- 策略:Customer Hash
- 策略:Zone Aware RoundRobin
- 还提供了完善的配置:连接超时、重试、重试的算法等
Hystrix:断路器
场景:web服务调用了数据库服务,数据库出问题无法响应,导致客户端超时。用户发现没反应,会重复操作,导致请求成倍增长。即使数据库很快恢复,也会由于后面堆积的大量请求而再次压垮,服务很长时间无法恢复。
解决方案:
- 服务方不可用的时候,后续请求不会再次访问服务方了
- 服务不可用的时候,给用户一个更好的响应
- 如:对时效性不高的请求,从缓存中取出数据返回给用户
Zuul:APIGateway
作用:
- 对外提供RESTful API
- 提供路由服务
- 负载均衡
- 权限控制
- 客户端通过ELB接入云服务
- 请求到Zuul,Zuul对外提供统一的接口(服务路由功能)
- 对内请求:转发给了不同的Service
- Edge Service对外提供RESTful API,Zuul对这些API进行整合
- 如何转发请求到Edge Service:
- 情况一:整合SpringCloud家族的一个东东,通过服务的id访问到EdgeService,且是负载均衡德
- 情况二:不整合,单独使用Zuul,通过url映射的方式,找到对应微服务
- 如:某个url开头的请求,转发到对应微服务的地址上,地址就是Edge Service的地址
- 对内请求:转发给了不同的Service
Spring Cloud Config:解决不同环境的配置问题(测试环境、开发环境、线上环境)
不同环境配置问题的解决方案们:
情景:两个环境 - 测试环境和生产环境
原始方案:
- 代码从测试环境切到生产环境后,手动修改代码中的环境配置 - 问题:改动了代码可能产生Bug
- 排除文件的方式
- 代码层面写上,然后通过环境变量指定的方式