一、What
微服务架构是指,组成一个整体系统是由许多不同的子系统构成,这些子系统独立存在,而又会互相调用。一个典型的微服务系统有以下几个组成部分:
- 注册中心
- 配置中心
- 网关
- 各个独立的子模块
二、使用场景
数据量千万级别,访问量千万级别
三、优势
- 复用性,消除代码拷贝
- 专注性,防止复杂性扩散
- 解耦和,消除公共库耦合
- 高质量,SQL稳定有保障
- 易扩展,消除数据库耦合
- 高效率,调用方研发效率提升
四、粒度
- 统一服务层
- 一个子业务一个服务
- 一个数据库一个服务
- 一个接口一个服务
最佳实践是:一个子业务一个服务
五、高可用
- 怎么知道是否高可用:关闭线上任何一台机器,线上服务都不会down掉
- 方法论:集群化(冗余) + 故障自动转移
- 具体步骤:
a. 反向代理层:反向代理冗余,VIP + Keepalived
b. 站点应用层:站点应用层冗余,nginx自动感知
c. 服务层:服务层冗余,服务连接池自动感知服务层的存活
d. 缓存层:缓存层冗余,缓存客户端 + 缓存层哨兵发现机制
e. 数据库读:数据库Slave节点冗余,数据库连接池自动发现数据库是否可用
f. 数据库写:数据库Master节点冗余,VIP + Keepalived
六、高并发
- What:通过设计保证系统能够同时并行处理很多请求。概念有:响应时间,QPS, TPS,并发用户数等
- How:垂直扩展(scale up)、水平扩展(scale out)
- 具体步骤:
a. 反向代理层:DNS轮询
b. 站点应用层:nginx反向代理
c. 服务层:服务层连接池
d. 数据层:数据的水平切分(按照数据范围,或者数据哈希的方式来进行水平扩展)
七、负载均衡
-
What:它通常是指,将请求/数据【均匀】分摊到多个操作单元上执行
-
How:
同构环境下,重点在于 “均匀”
异构环境下,重点在于 “负载与能力匹配” -
同构环境下,负载均衡的具体步骤:
在同构环境下,负载均衡的实现基本上不需要额外的支持,在实现高并发、高可用的基础设施中:
a. 客户端到反向代理由dns轮询完成;
b. 反向代理到站点由nginx来完成;
c. 站点到服务由连接池完成;
d. 服务到数据层也是由数据层框架的客户端提供的连接池来完成。
因为是同构的,所以均衡策略是简单,轮询,随机的方式都可以实现。 -
异构环境下,负载均衡的具体步骤:
静态权重:
What:静态权重和同构负载均衡策略几乎一样,假设三台机器,如果配置负载为1:1:1,那么就是负载均衡,所以可以把负载均衡看成是静态权重的一个特例;
优点:快速,简单
劣势:是静态的,无法实时变化,过载保护也实施不了动态权重:
What:根据服务的处理能力动态的变化其权重,权重的大小体现了负载路由到这台机器的概率;
How:
a. 识别服务处理能力:成功返回说明能力可以,超时说明不能承受当前流量;
b. 设计动态权重:成功加小分,失败减大分;
优点:可以动态的体现异构环境下服务的处理能力,并分配与之能力想匹配的负载
劣势:负载,需要额外开发 -
过载保护:
What:当服务过载的时候很有可能造成我们说的“雪崩”,服务的流量到达处理能力的峰值,随之再增加流量,处理成功的请求直线下降,服务进入不可用的状态。
How:
a. 静态方式:
静态的过载就是设定一个流量阈值,超过这个阈值就不会再有多的请求了。b. 动态方式:
动态的过载保护和动态的负载均衡策略是相似的。- 连接代表服务,分值代表处理能力
- 处理成功加小分,失败减大分
- 临界边界(比如连续失败1次)喘口小气(减小流量,持续时间短)
- 死亡状态(比如连续失败2次或以上)喘口大气(没有流量,持续时间长)
如果过载保护实施的是某一节点,则本来由该节点处理的请求转发到其它节点;如果所有节点(即整个集群)都是过载状态,则丢弃请求;所以说集群的过载保护策略和某一节点的策略是不同的,一个是丢,一个是转。
连接池
- What:相对于短连接(使用后即关闭),连接池是一个维护如果若干个连接的一个池子,需要用的时候从池子里去,用完之后放入池子,池子里的连接是可以重复使用的。
- Why:如果每次都是建立连接,使用连接收发请求,关闭连接,当遇到高并发的场景,建立连接和关闭连接会成为瓶颈。
- How:
a. 核心接口:初始化;拿连接;放回连接
/*数据结构很简单,总共是两个数组,一个是表示所有真正连接的数组,还有一个是第三行出现的一个lock数组,
lock数组的作用即是表征下标对应的连接的状态,当前是否被占用。*/
init() {
for i to N {
Array DBClientConnection[i] = new()
Array DBClientConnection[i] -> connect()
Array lock[i] = 0
}
}
/*拿连接的过程也非常简单,首先就是遍历锁数组,如果为0,那么设置锁为1,返回连接即可*/
GetConnection() {
for i to N {
if(Array lock[i] == 0) {
Array lock[i] = 1
return Array DBClientConnection[i]
}
}
}
/*放回连接只需要把lock设置为0就可以了*/
FreeConnection(c) {
for i to N {
if(Array DBClientConnection[i] == c) {
Array lock[i] = 0
}
}
}
b. 其他考虑因素:
1) 复杂度还可以优化为O(1)
2) 连接的可用性检测,如果连接失效了,需要重新连接,并替换原来的连接
3) 如果下游服务器故障,失效连接要剔除掉,以实现故障的自动转移,从而实现高可用
4) 如果下游有新增的节点,需要动态扩充连接池,以实现服务自动发现,从而实现扩展性。可以通过监控配置文件的方式(比如MD5,),如果改变则重新载入;或者通过配置中心回调
5) 要保证连接选取的概率,实现负载均衡。可以通过轮询,随机,静态权重,动态权重等方式实现基于连接池的负载均衡
参考
- 高可用:https://www.jianshu.com/p/dcb73d907342
- 高并发:https://www.jianshu.com/p/be66a52d2b9b
- 负载均衡1:https://www.jianshu.com/p/41f437542ffc
- 负载均衡2:https://blog.csdn.net/Sunsscode/article/details/107693303