php网站建设原码,俄罗斯在线 网站制作,学会了vue 能搭建一个网站平台,山东手机版建站系统信息微服务架构设计的中心思想是将服务进行拆分#xff0c;但是在这个过程中#xff0c;如果被依赖的服务发生奔溃#xff0c;就会引起一系列问题。为了解决这个问题#xff0c;就会引入重试的机制#xff0c;重试又会引入幂等性的问题#xff0c;下面我们就分析这个过程但是在这个过程中如果被依赖的服务发生奔溃就会引起一系列问题。为了解决这个问题就会引入重试的机制重试又会引入幂等性的问题下面我们就分析这个过程然后探讨一下常见的解决方案。
一、相关概念
1、雪崩
定义 服务雪崩效应是一种因“服务提供者的不可用”导致“服务调用者不可用”并将不可用逐步放大的现象。如下图所示 上图中A为服务提供者B为A的服务调用者C和D是服务B的调用者。当A不可用引起B的不可用并将不可用逐渐放大到C和D服务雪崩就形成了。形成原因 服务雪崩的过程可以分为三个阶段 1、服务提供者不可用2、重试加大请求流量3、服务调用者不可用 服务雪崩的每个阶段都有可能由不同的原因造成总结如下 应对策略 在高并发项目中如何提高并发量是主要的目标当然也要考虑成本如果为了解决可能会突然出现的高并发或者因为网络环境的问题导致的被调用者反应很慢这种情况如果还是加入很多的机器作为后备就很不经济在这个过程中要防止服务雪崩一般的情况就是给调用增加超时如果超过一定的时间就重试调用这也可以提高用户体验不至于因为一点网络问题就给客户返回不可用。
2、超时和重试
超时 超时是为了保护服务避免调用服务因为响应慢而也变得特别慢这样调用服务就可以尽量保持原有的性能。重试 如果被调用服务只是偶尔的抖动那么超时后直接放弃不做后续处理就会导致当前服务请求错误也会带来业务方面的损失。对于这种偶尔的抖动可以在超时后重试一下重试如果可以正常返回那么这次请求就被拯救了能够正常给前端返回数据。只不过要比原来的响应慢一点。在负载均衡中的重试也可以换一台机器进行调用因为原来机器可能由于临时负载高而性能下降重试会增加其想能问题而换一台机器得到更快返回的概率也会更大一点。
3、幂等性
幂等性概念 在编程中一个幂等的操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。幂等函数或者幂等方法是指可以使用相同参数重复执行并能获得相同结果的函数。这些函数不会影响系统状态也不用担心重复执行会对系统造成改变。 总的来说迷瞪就是一个操作不论执行多少次产生的效果和返回的结果都是一样的满足restful的http请求类型的幂等性分析 get get请求是获取操作只是查询天生支持幂等性post post请求用于新增数据多次执行会产生多条数据这种接口需要考虑幂等性put put请求有两种情况如果是简单的更改比如将一个商品的数量数据改成一个确定的值多次执行还是同样的值只是消耗了计算机的性能对最终的数据没有影响这样的接口是满足幂等性的。 但是如果是类似于累加修改这种操作多次执行会产生不同的结果就需要考虑幂等性。 所以put请求要补考考虑幂等性是需要具体问题具体分析的。delete delete请求和put请求一样如果是简单的删除操作大多数情况多次删除的结果一致那就不需要考虑幂等性。如果不一致还是要考虑幂等性的。
二、幂等性常见的解决方案
1、唯一索引防止新增脏数据
比如新建用户的时候用手机号作为唯一索引那么即使重试也会只新建一个用户不会因为多次重试导致当前用户被注册了很多次。
2、token机制防止页面重复提交
在前端提交之前生成一个token在提交以后可以判断该token是否已经进行了处理这样可以防止数据的重复提交。token的特点要申请一次有效性可以限流 注意如果要用redis校验token建议使用redis删除来判断token删除成功代表token校验通过如果采用select delete来校验token由于操作redis的次数多存在并发问题所以不建议使用。
3、悲观锁
获取数据时加锁其他数据会被阻塞在这里执行完成后再判断是否已经提交过这样可以防止多次提交但是性能不太好数据锁定的时间可能会很长根据实际情况选用。
4、乐观锁
根据数据库数据增加版本号的方式或者通过限制条件增加乐观锁和悲观锁的原理一样但是不会锁住表。
5、分布式锁
用redis等中间件做分布式锁可以防止并发操作如果已经存在这个数据其他提交就可以不用再进行操作了。
6、select insert
对于并发不太高的系统可以采用先查询一下如果存在就不用再操作的方式实现幂等性但是并发高的核心系统不能这样做因为没有加锁insert操作可能会被多次执行。
7、对外提供的接口实现幂等性
如银联提供的付款接口需要接入商户提交付款请求时附带source来源seq序列号等用source seq在数据库中做唯一的索引防止多次付款。
三、grpc实现超时和重试的调用
在grpc中可以在调用时增加grpc.DialOption的方式来实现超时重试的机制。 在用proto生成的接口中调用的时候可以增加grpc.CallOption的调用参数我们可以在这里增加重试的功能
type UserClient interface {GetUserList(ctx context.Context, in *PageInfo, opts ...grpc.CallOption) (*UserListResponse, error)GetUserByMobile(ctx context.Context, in *MobileRequest, opts ...grpc.CallOption) (*UserInfoResponse, error)GetUserById(ctx context.Context, in *IdRequest, opts ...grpc.CallOption) (*UserInfoResponse, error)CreateUser(ctx context.Context, in *CreateUserInfo, opts ...grpc.CallOption) (*UserInfoResponse, error)UpdateUser(ctx context.Context, in *UpdateUserInfo, opts ...grpc.CallOption) (*emptypb.Empty, error)CheckPassWord(ctx context.Context, in *PasswordCheckInfo, opts ...grpc.CallOption) (*CheckResponse, error)
}在接口中给定的参数只是对这个接口起作用要想让所有的调用都起作用我们可以在进行连接的时候就指定grpc.DialOption这样对这个连接中的接口都会起作用。 Dial方法的声明如下
func Dial(target string, opts ...DialOption) (*ClientConn, error) {return DialContext(context.Background(), target, opts...)
}下面是我们在连接时指定重试的示例代码实现
import (contextfmttimegoogle.golang.org/grpc/codesgrpc_retry github.com/grpc-ecosystem/go-grpc-middleware/retrygoogle.golang.org/grpcOldPackageTest/grpc_test/proto
)func main() {// 增加一个耗时打印的interceptor interceptor : func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {start : time.Now()err : invoker(ctx, method, req, reply, cc, opts...)fmt.Printf(耗时%s\n, time.Since(start))return err}var opts []grpc.DialOptionopts append(opts, grpc.WithInsecure())retryOpts : []grpc_retry.CallOption{grpc_retry.WithMax(3),// 最大的重试次数grpc_retry.WithPerRetryTimeout(13 * time.Second),//超时时间grpc_retry.WithCodes(codes.Unknown, codes.DeadlineExceeded, codes.Unavailable),// 对于哪些返回状态进行重试}opts append(opts, grpc.WithUnaryInterceptor(interceptor))//这个请求应该多长时间超时 这个重试应该几次、当服务器返回什么状态码的时候重试opts append(opts, grpc.WithUnaryInterceptor(grpc_retry.UnaryClientInterceptor(retryOpts...)))conn, err : grpc.Dial(127.0.0.1:50051, opts...)if err ! nil {panic(err)}defer conn.Close()c : proto.NewGreeterClient(conn)r, err : c.SayHello(context.Background(), proto.HelloRequest{Name: bobby})if err ! nil {panic(err)}fmt.Println(r.Message)
}后记 个人总结欢迎转载、评论、批评指正