博主之前做过恒丰银行代收付系统(相称于支付接口),包括现在的oltpapi交易接口和虚拟业务的对外供应数据接口。总之,当你做了很多项目写了很多代码的时候,就须要回过分来,多总结总结,这样你会看到更多之前写代码的时候看不到的东西,也能更明白为什么要这样做。
做接口须要考虑的问题什么是接口接口无非便是客户端要求你的接口地址,并传入一堆该接口定义好的参数,通过接口自身的逻辑处理,返回接口约定好的数据以及相应的数据格式。
接口由于本身的性子,由于和互助方对接数据,以是有以下几点须要在开拓的时候把稳:

1.定义接口入参:写好接口文档
2.定义接口返回数据类型:一样平常都须要封装成一定格式,确定返回json还是xml报文等
见如下返回数据定义格式:
package com.caiex.vb.model; import java.io.Serializable;import javax.xml.bind.annotation.XmlAccessType;import javax.xml.bind.annotation.XmlAccessorType;import javax.xml.bind.annotation.XmlType; @XmlAccessorType(XmlAccessType.FIELD)@XmlType(name = "Result", propOrder = { "resultCode", "resultMsg" })public class Result implements Serializable { private static final long serialVersionUID = 10L; protected int resultCode; protected String resultMsg; public int getResultCode() { return this.resultCode; } public void setResultCode(int value) { this.resultCode = value; } public String getResultMsg() { return this.resultMsg; } public void setResultMsg(String value) { this.resultMsg = value; }}
package com.caiex.vb.model; import java.io.Serializable; public class Response implements Serializable { private static final long serialVersionUID = 2360867989280235575L; private Result result; private Object data; public Result getResult() { if (this.result == null) { this.result = new Result(); } return result; } public void setResult(Result result) { this.result = result; } public Object getData() { return data; } public void setData(Object data) { this.data = data; } }
3.确定访问接口的办法,get or post等等,可以根据restful接口定义规则RESTful API:RESTful API
4.定义一套全局统一并通用的返回码,以帮助排查问题;
public static int NO_AGENT_RATE = 1119; //未找到兑换率 public static int SCHEME_COMMIT_FAIL = 4000; //方案提交失落败 public static int SCHEME_CONFIRMATION = 4001; //方案确认中 public static int SCHEME_NOT_EXIST = 4002; //方案不存在 public static int SCHEME_CANCEL= 4005; //方案不存在 //。。。。
5.统一的非常处理:该当每个别系都须要一套统一的非常处理
package com.caiex.vb.interceptor; import javax.servlet.http.HttpServletRequest; import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.web.bind.annotation.ControllerAdvice;import org.springframework.web.bind.annotation.ExceptionHandler;import org.springframework.web.bind.annotation.ResponseBody; import com.caiex.vb.model.Response; @ControllerAdvice@ResponseBodypublic class GlobalExceptionHandler { private Logger logger = LoggerFactory.getLogger(this.getClass()); / 所有非常报错 @param request @param exception @return @throws Exception / @ExceptionHandler(value=Exception.class) public Response allExceptionHandler(HttpServletRequest request, Exception exception) throws Exception { logger.error("拦截到非常:", exception); Response response = new Response(); response.setData(null); response.getResult().setResultCode(9999); response.getResult().setResultMsg("系统繁忙"); return response; } }
6.拦截器链设置:互助方访问接口的时候,会根据你接口定义好的传参访问你的接口做事器,但是会存在接口参数类型缺点或者格式不对,必传参数没传的问题,乃至一些恶意要求,都可以通过拦截器链进行前期拦截,避免造成接口做事的压力。
学习资料:Java进阶视频资源
还有很主要的一点,加签验签也可以在拦截器设置。继续WebMvcConfigurerAdapter实现springboot的拦截器链。实现HandlerInterceptor方法编写业务拦截器。
package com.caiex.vb.interceptor; import javax.annotation.Resource;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang3.StringUtils;import org.apache.logging.log4j.LogManager;import org.apache.logging.log4j.Logger;import org.springframework.stereotype.Component;import org.springframework.web.servlet.HandlerInterceptor;import org.springframework.web.servlet.ModelAndView; import com.alibaba.fastjson.JSON;import com.caiex.redis.service.api.RedisApi;import com.caiex.vb.model.Response;import com.caiex.vb.utils.CaiexCheckUtils; @Componentpublic class SignInterceptor extends BaseValidator implements HandlerInterceptor{ private Logger logger = LogManager.getLogger(this.getClass()); @Resource private RedisApi redisApi; public void afterCompletion(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, Exception arg3) throws Exception { // TODO Auto-generated method stub } public void postHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, ModelAndView arg3) throws Exception { // TODO Auto-generated method stub } public boolean preHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2) throws Exception { if(isTestIpAddr(arg0)){ return true; } String securityKey = redisApi.hGet("securityKey", arg0.getParameter("agentid")); if(StringUtils.isEmpty(securityKey)){ Response response = new Response(); response.setData(null); response.getResult().setResultCode(8001); response.getResult().setResultMsg("短缺私钥, 渠道号:" + arg0.getParameter("agentid")); logger.error("短缺私钥, 渠道号:" + arg0.getParameter("agentid")); InterceptorResp.printJson(arg1, response); return false; } if(StringUtils.isEmpty(arg0.getParameter("sign")) || !arg0.getParameter("sign").equals(CaiexCheckUtils.getSign(arg0.getParameterMap(), securityKey))){ Response response = new Response(); response.setData(null); response.getResult().setResultCode(3203); response.getResult().setResultMsg("参数署名认证失落败"); logger.error("参数署名认证失落败:" + JSON.toJSONString(arg0.getParameterMap()) + " securityKey = " + securityKey); InterceptorResp.printJson(arg1, response); return false; }else{ return true; } } }
package com.caiex.oltp.config; import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.ComponentScan;import org.springframework.context.annotation.Configuration;import org.springframework.web.servlet.config.annotation.EnableWebMvc;import org.springframework.web.servlet.config.annotation.InterceptorRegistry;import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import com.caiex.oltp.interceptor.APILimitRateValidator;import com.caiex.oltp.interceptor.CommonValidator;import com.caiex.oltp.interceptor.DDSAuthValidator;import com.caiex.oltp.interceptor.QueryPriceParamsValidator;import com.caiex.oltp.interceptor.TradeParamsValidator; @EnableWebMvc@Configuration@ComponentScanpublic class WebAppConfigurer extends WebMvcConfigurerAdapter { @Bean CommonValidator commonInterceptor() { return new CommonValidator(); } @Bean DDSAuthValidator ddsAuthInterceptor() { return new DDSAuthValidator(); } @Bean QueryPriceParamsValidator queryPriceParamsInterceptor() { return new QueryPriceParamsValidator(); } @Bean TradeParamsValidator tradeParamsInterceptor() { return new TradeParamsValidator(); } @Bean APILimitRateValidator aPILimitRateInterceptor() { return new APILimitRateValidator(); } @Override public void addInterceptors(InterceptorRegistry registry) { //访问速率限定 registry.addInterceptor(aPILimitRateInterceptor()) .addPathPatterns("//"); //.addPathPatterns("/price/getPriceParam"); //参数署名认证 registry.addInterceptor(ddsAuthInterceptor()) .addPathPatterns("/tradeState/") .addPathPatterns("/recycle/") .addPathPatterns("/matchInfo/") .addPathPatterns("/price/tradeTicketParam"); //公共参数检讨 registry.addInterceptor(commonInterceptor()) .addPathPatterns("/price/tradeTicketParam") .addPathPatterns("/tradeState/") .addPathPatterns("/recycle/"); //询价参数校验 registry.addInterceptor(queryPriceParamsInterceptor()) .addPathPatterns("/price/getPriceParam"); //交易参数检讨 registry.addInterceptor(tradeParamsInterceptor()) .addPathPatterns("/price/tradeTicketParam"); super.addInterceptors(registry); }}
7.token令牌和sign数字署名实现数据保密性。
创建令牌(Token)为担保要求的合法性,我们供应第三方创建令牌接口,某些接口须要通过token验证的合法性,以免遭受造孽攻击。
token过期韶光目前暂时定为1天,由于考虑到互助方每每是分布式环境,多台机器都有可能申请token,为了降落互助方担保token同等性的难度,调用接口创建token成功往后一分钟以内,再次要求token返回的数据是一样的。
获取私钥获取用于数字署名的私钥,第三方获取的私钥需妥善保存,并定期更新,私钥只参与数字署名,不作为参数传输。
数字署名办法:
参数署名;署名办法:所有值不为null的参数(不包括本参数)均参与数字署名,按照“参数名+参数值+私钥”的格式得到一个字符串,再将这个字符串MD5一次便是这个参数的值。(示例:h15adc39y9ba59abbe56e057e60f883g),以是须要先获取私钥。
验签办法:
将用户的所有非null参数放入定义好排序规则的TreeSet中进行排序,再用StringBuilder按照按照“参数名+参数值+私钥”的格式得到一个字符串(私钥从redis拿),再将这个字符串MD5一次便是这个参数的值。将这个值与用户传来的sign署名比拟,相同则通过,否则不通过。
private String createToken(){ String utk = "Msk!D"+System.currentTimeMillis()+"UBR&FLP"; logger.info("create token --- "+Md5Util.md5(utk)); return Md5Util.md5(utk); }
8.接口限流
有时候做事器压力真的太大,以防交易接口被挤去世,就可以对一些其他不影响紧张业务功能并且打算量大的接口做限流处理。RateLimit--利用guava来做接口限流,当接口超过指定的流量时,就不处理该接口的要求。详细可看RateLimit。也可参考其他限流框架。
9.协议加密,http升级成https;
为什么要升级呢,为了担保数据的安全性。当利用https访问时,数据从客户端到做事断,做事端到客户端都加密,纵然黑客抓包也看不到传输内容。当然还有其他好处,这里不多讲。但这也是开拓接口项目须要把稳的一个问题。
如何提高接口的高并发和高可用接口开拓好了,接下来就谈论接口的可用性问题。首先我们要将高并发和高可用区分一下,毕竟高可用是在可用的情形,只是很慢或者效率不高。实在也可以归为一类问题,但是不主要啦,主要的是怎么提高你写的接口的访问速率和性能。
接口的高并发办理方案(实在没有唯一答案,业界针对不同业务也有很多不同的方法)当访问一个接口获取数据时,创造返回很慢,或者总是超时,如果打消网络的缘故原由,那便是接口做事器压力太大,处理不过来了。在世界杯期间,我们查看后台日志总是connection by reset和borker pipe和一些超时问题。
这时候,你可能碰着了高并发和高可用问题。但是,不管碰着什么问题,都不能臆断和乱改,你得须要找到慢的缘故原由,才能对症下药,乱改可能会导致其他问题的涌现。首先,办理高并发问题的三个方向是负载均衡,缓存和集群。
学习资料:Java进阶视频资源
负载均衡我们利用的是阿里云做事器的负载均衡,后台分布式做事管理,我们运维小哥哥搭建了一套k8s,可以自由在k8s上扩展做事节点,各个做事结点也能随内存的利用自动漂移,不用多说,k8s真的很厉害,感兴趣的同学可以详细去学。那么问题来了,阿里云的负载均衡怎么对应到k8s的负载均衡呢?
这个涉及到了k8s的service暴露的一些特点,大略说便是k8s把所有集群的做事都通过指定的内部负载均衡,在指定的做事器上暴露,然后我们又把这几个做事器接在阿里云负载均衡下,这个涉及的细节和配置很多。当然,除nginx外,还有其他负载均衡办理方案,软件硬件都有,硬件如f5等。
阿里云的nginx负载均衡,我们利用的是加权轮询策略,实在轮询是最低效的办法;
这便是最基本的负载均衡实例,但这不敷以知足实际需求;目前Nginx做事器的upstream模块支持6种办法的分配:
负载均衡策略
轮询默认办法weight权重办法ip_hash依据ip分配办法least_conn最少连接办法fair(第三方)相应韶光办法url_hash(第三方)依据URL分配办法
集群首先,通过排查问题,创造是oltpapi接口做事处理要求很慢,大量要求过来,总是超时和中断连接,这时候,我们想着最大略的方法便是加机器,给oltp接口做事多加几台机器。
嗯,统统都很完美,如预期进行,但是加到一定数量,你创造,怎么不起效果,异步相应还是很慢,或者更直不雅观的说,行列步队涌现了严重的堆积。这时候,你创造涌现了新的问题或者瓶颈,这个问题已经不是说加oltp做事器能办理了,那么,就须要去重新定位问题。创造是堆积,堆积便是生产者过快,导致消费者消费不过来,这时候,你就须要增加消费者的消费数量。给风控系统多加几台机器,让消费者和生产者达到一定平衡。
这里有个误区,你可能以为是rocketmq的broker数量过少,增加broker数量,实在当消费者和生产者保持一样的速率时,肯定不对堆积,按照原始的broker数量就足够。但是增加broker也会使得得到尽快的处理,提升一定效率。
缓存当加机器不能办理问题时,或者说没那么多做事器可利用时,那么就要重代码层面办理高并发问题。Redis 是一个高性能的key-value数据库,当获取数据从数据库拿很慢时,就可以存储到redis,从redis取值。
用ConcurrentHashMap缓存工具,并设置过期韶光redis缓存数据,结合spring定时任务定时获取不会常常改动的key提高利用redis的效率:比如利用mGet一次获取多个key....等接口高可用问题高可用问题该当上升到全体做事的架构问题上,便是说在搭建整体系统是就该当考虑到。高可用问题因此单点故障,访问速率慢的问题为主导。见 做事高可用
redis主从分布式(redis的单点故障和访问速率的提高和主从备份)分布式dubbo做事的zookeeper主从集群strom的主从集群...等总结下面对接口开拓做事做一些总结:
1.是拉还是推:当接口作为数据源时,还要考虑数据是让互助方主动过来拉还是数据有变革就推送呢,当然是推的效果更好,但是如何有效的推数据,不推许复数据等都是须要根据实际业务考虑的问题。
2.多台分布式做事器上,怎么担保交易的幂等和订单的唯一性当接口做事和互助方都处于分布式情形下,就很随意马虎涌现一个订单号申请多次交易要求,但是根据幂等性,一张彩票只能交易一次,并且每次不管何时要求,结果都该当一样不会改变。这种情形下,我们怎么担保唯一性呢,我们须要把该订单和订单状态存redis,每次要求时去看是否订单已存在。但可能这次交易不堪利,下次这张票还可以连续交易,可以天生新的订单号啊。
redis的setNX是一个很好的办理方案,意思是当存在该key时,返回false,当没有时,该key和value插入成功。用作检讨订单是否正在提交,如果是,则壅塞本次要求,避免重复提交 ,可以设置过期韶光3s。提交之前锁定订单,防止重复提交。
3.处理韶光超过10s,自动返回该订单交易失落败总之,博主创造,在高并发场景下,导致做事崩溃的缘故原由还是redis和数据库,可能是redis读写太慢,或者数据库的一些sql利用不当,或者没建索勾引致读写很慢。
总之,这是一条很漫长的路,我们都须要逐步积累履历和学习古人更精良的办理办法。