服务无状态化定义
1、冗余部署的多个模块(进程)完全对等
2、请求提高到冗余部署的任一模块,处理结果完全一样
3、模块不存储业务的上下文信息
4、仅根据每次请求携带数据进行相应的业务逻辑处理
比如网关的用户信息缓存, 当网关挂了以后,重启一个备用的网关,这些用户信息缓存就没有了,就不是无状态的。
再入像京东电商有一些一级类目、二级类目,缓存在业务逻辑层服务里。重启业务逻辑层后,业务逻辑层也会加载到内存中,这些服务都是对等的,就是无状态的、幂等的
服务无状态化的目的
1、快速扩容服务
服务无状态的目的其实也是为了快速扩容。
2、弹性缩容服务
服务无状态化案例
用户Session数据
1、登录方式
用户名+密码
手机号+验证码
2、登录成功
生成用户凭证(session)
AES(UID+Timestmap+校验码) TTL 过期时间
用户Session数据存放在哪里?
用户Session需要存放到离用户最近的地方,所以我们会存在网关层。
用户Session数据如何存放?
直接存放网关层
存在外部存储
用户Session 数据直接存储网关层
单点
网关层有状态化
负载均衡系统(F5/LVS/Nginx)->session共享->接入层。。。。
用户Session 数据存储客户端
高可用
网关层有状态化
Session 丢失问题
客户端(Session/Native/Cookie) ->负载均衡系统(F5/LVS/Nginx)->接入层...
用户Session 数据外部存储
网关层无状态化
Session 数据高可用
负载均衡系统(F5/LVS/Nginx)->接入层->session 集群
redis-cluster mget 其实是通过客户端来做这些事情,除非他们都在一个分片上。
一致性哈希的问题:假如APP 登录到 一致性哈希的A->B->C->A 环形节点,当A节点挂了以后,就顺时针飘逸到B节点了,这时候在B节点上生成数据,生成数据以后就返回给APP了,APP再次访问的时候,此时如果A节点重启了,APP在A节点的数据是已经是out了,B节点是最新的数据了,这就涉及到整个数据归置的问题,归置问题不太好解决,如果是session数据还好,直接在A节点上再生成一次就ok了,就是比较折腾。如果数据一致性比较高的情况下,一致性hash是不太容易好做的事,这是网络中断造成的漂移问题还是要注意的。
服务负载均衡设计与实践
负载均衡系统
硬件
F5
A10
Radware
软件
LVS 4层
Nginx 7层
HAProxy 4层或7层
反向代理VS正向代理
负载均衡算法
Dubbo LoadBalance
Random 随机,按权重设置随机概率
RoundRobin 轮询,按约定后的权重设置轮询比率
ConsistentHash 一致性Hash,相同参数的请求总是发到同一提供者。
广义负载均衡
上面的都是狭义的负载均衡。
广义的负载均衡
完整的故障恢复机制
1、故障自动发信
2、故障服务自动剔除,服务熔断机制
3、请求自动重试
4、服务恢复自动发现
服务注册到Zookeeper ,注册信息 IP:Port:name ,网关通过Watch(name)来发现这些服务
网关层将所有的请求结果放到一个队列里面,
网关层单独开启一个线程会取查询上一秒请求队列的返回状态和耗时。通过计算错误百分比。
当某个服务的错误率超过30%,这个服务有十个工作线程,网关就将这个十个线程关掉,这样网关就将这个服务熔断。
网关层熔断已经将故障的业务服务干掉了,但是这个故障的业务服务在Zookeeper里面还保持正常,如何优雅的将故障业务服务从zookeeper里面干掉呢?
网关层发一个命令到控制中心,这个命令包括要关闭的服务的IP:Port:Name 控制中心再发送命令给故障服务器的Agent。这个Agent来控制故障服务器的服务的重启。
当然Agent收到请求之后也不是马上重启,按如下顺序
1、jstack 2次
2、kill 这个进程
3、sleep 6秒
4、start
这么做的目的就是 如果你的重启时间是小于zookeeper的两次心跳的话,zk 是不会知道你的进程是挂了,就不会通知你的网关了。
重启是为了保证故障恢复,jstack 打印进程堆栈信息,打印两次是为了做上下文对照。如果你的程序有死循环,那么两次jstack打印的是一样的。
为什么不用hystrix
1、它是客户端实现 ,需要引用比较重的jar包
2、它不是平台化的东西,我们需要一个单独平台专门做这个事情。
3、很多接口要实现hystrix的 命令(common key )
水平分层架构案例
1、业务逻辑层1故障
谁来发现
网关层
注册中心:zookeeper etcd consul
如何发现
能否发现一切问题
2、服务熔断机制
Netflex OSS Hystrix
熔断后恢复机器
机器类型
物理机/虚拟机 Kill Process
容器化 Kill Process Kill Docker/Pod
停止系统时,比如后台一些批量长时间任务,如何优雅停机
kill -15 pid
通过该命令发送一个关闭信号给到jvm, 拒绝新的请求,然后就开始执行 Shutdown Hook 了,你可以做很多:
1、关闭 socket 链接
2、清理临时文件
3、发送消息通知给订阅方,告知自己下线
4、将自己将要被销毁的消息通知给子进程
5、各种资源的释放 ...
参考 :https://cloud.tencent.com/developer/article/1370525
单个nginx的并发达到10万,主要看机器配置,如果内存是192G内存,配置 40核,万兆网卡。
线上环境一般设置ulimit -n fd 1024万 或者 102.4万,尽量设置大一点,无非是多消耗一点内存。
比如网关调用业务A,在机器1上,超时了会重试,重试到第二台机器上,1次超时不会触发熔断,只有错误率达到30%以后才会触发熔断。
但是如果是写数据,写到机器1上成功了,但是返回超时了,网关会重试到机器2上,这就是写幂等的问题。只要保证写幂等就解决了这个问题。
针对下订单->减库存->支付 这个流程 下订单、减库存成功了,但是支付失败了,这种情况应该看成是一种事务,如果支付失败了,就需要事务补偿就好了。
服务幂等设计与实践
读请求-read 无需做幂等 因为读请求不会对数据发生改变
写请求-write 需要做幂等,因为写请求会对数据发生改变。
幂等定义
1、请求层面
保证请求重复执行和执行一次结果相同
f...f(f(x))=f(x) x 是参数,f是执行函数/方法
2、业务层面
同一用户不重复下单
商品部超买
MQ消费端去重
幂等目的
1、请求重试 某银行幂等案例
2、结果灾难性 转账 交易
幂等范围
读/写请求层面
请求对数据造成改变
写请求
架构层层面
哪些层会对数据造成改变
反向代理
网关层
业务逻辑层
数据访问层
水平分层架构案例
image.pngNginx、网关层、业务逻辑层都不会对数据发生改变,所以我们无需做幂等处理。数据访问层是直接操作DB,所以会发生数据改变,所以要对数据访问层做幂等处理。
数据访问层对外提供了CRUD 四个接口,其中R不需要做幂等处理。
数据访问层
CRUD
1、create/insert
insert user values (uid,age,sex,ts);
insert user values(77,29,女,2020.03.19)
主键分为业务主键、自增主键,如果数据是业务主键,则是天然幂等的。因为uid是PK 键 ,如果冲突是插入不进去的。
如果是自增主键,则不是幂等的,每次插入都会id 都会加1
如果user 表里有一个唯一索引unique token 则也能保持幂等。
回到问题的本质,自增主键的弊端:
mysql 用的半同步主从结构,当主库id 自增到1000,从库的id 已经自增到998,如果主库挂了,那从库切换为主,你的id 是以1000开始还是998开始?
数据访问层可以用业务主键作为数据库主键,业务主键由分布式ID生成器来生成。用户由用户ID、订单有订单id、商品有商品Id。
百度是不允许用数据库自增主键的。
2、read/select
select * from user where uid=58
查询是天然幂等的
3、update
update user set age=18 where uid=77; 绝对值修改
update user set age++ where uid=77; 相对值修改
第一个是幂等的
第二个不是幂等的
Delete
Delete from user where uid=77; 绝对值删除
Delete from user where uid in bottom 10; 相对值删除
第二个是 删除排名靠后的10名是不幂等的,第二种一般是先查询出来,然后用把id 转换成绝对值再删除。
Update 幂等案例
案例一:QQ离线消息读取
天然幂等
当你QQ离线的时候,服务器将QQ消息修改为离线消息,无论你消息修改多少遍,它还是幂等的。
案例二:年龄增加1岁
1、增加where 条件
where age=18;
update user set age=18 where uid=77 and age=18;
2、相对修改转换成绝对修改
set age=19
update user set age=19 where uid=77
上面两种针对互联网并发量很大的大规模情况下,因为场景比较复杂,其实是性能不行的。因为这样是每条记录加上行锁,性能会有损耗, where条件最好带索引 比如主键
案例三:电商平台购买商品
商品价格¥10000
确认收货
订单状态 1、打款;2、状态修改;
分布式事务
订单状态 status =1、已收货 2、已打款
状态修改
打款和状态修改不是在同一个DB中,比如打款成功,修改状态失败,这就不是幂等的问题,这是一个分布式事务的问题。
网友评论