服務(wù)開發(fā)規(guī)范

服務(wù)交互的方式有很多種,dubbo、grpc敞映、soap webservice献宫、restful webservice、異步消息(rocketmq、kafka、rabbitmq等)

1 如何選型?

怎么選型鳖擒?看具體應(yīng)用場(chǎng)景,沒(méi)有一刀切烫止。只有理解了各種交互方式的優(yōu)缺點(diǎn)蒋荚,才能根據(jù)具體的場(chǎng)景做具體分析。你的應(yīng)用場(chǎng)景關(guān)注的是什么質(zhì)量屬性馆蠕,解耦期升、實(shí)時(shí)性惊奇、高性能、高并發(fā)播赁、易用性颂郎、可讀性?

一般來(lái)講:交易型接口用restful http服務(wù)容为;異步非實(shí)時(shí)類用MQ乓序;數(shù)據(jù)分析類接口需求采用kafka、rocketmq等進(jìn)行數(shù)據(jù)推送坎背。

1.1 不推薦的

不推薦使用soap協(xié)議webservice替劈,大家在使用soap協(xié)議的時(shí)候真正理解什么是soap嗎?什么是真正xml格式(不是返回xml格式的字符串)得滤?有什么優(yōu)缺點(diǎn)陨献?

1.2 推薦的方式

你是性能狂魔嗎?
如果是性能狂魔懂更,追求極致的性能湿故,那必須基于socket通信協(xié)議,例如阿里內(nèi)部廣泛采用netty做為底層通信框架膜蛔,來(lái)實(shí)現(xiàn)dubbo、HSF等分布式服務(wù)框架脖阵。
一般場(chǎng)景下無(wú)需追求極致的性能皂股,生態(tài)好、簡(jiǎn)單命黔、清理呜呐、易用、云原生等質(zhì)量屬性便是我們的追求----該restful服務(wù)出場(chǎng)了悍募。而且http服務(wù)同樣能通過(guò)保持長(zhǎng)連接蘑辑,達(dá)到很高的性能。
服務(wù)開發(fā)模板:https://github.com/wuzuquan/microservice

2 服務(wù)開發(fā)規(guī)范

2.1 rest只是一種風(fēng)格坠宴,并非標(biāo)準(zhǔn)

什么意思呢洋魂,rest并不是一種技術(shù)標(biāo)準(zhǔn),無(wú)需嚴(yán)格按照網(wǎng)上的那些條條框框去開發(fā):
url代表資源喜鼓,通常情況下可以這么描述副砍,但實(shí)際業(yè)務(wù)場(chǎng)景很復(fù)雜,無(wú)需套用這種url命名方式庄岖,接口一定要簡(jiǎn)單豁翎、易懂。
get 隅忿、post心剥、put邦尊、delete對(duì)應(yīng)CRUD,但put优烧、delete對(duì)防火墻來(lái)說(shuō)都是不夠友好的蝉揍,通常會(huì)被過(guò)濾掉。所以棄用put匙隔、delete疑苫。get操作一般用于普通查詢,如果要傳復(fù)雜參數(shù)纷责,建議使用post方式捍掺,把參數(shù)放置于消息體中,而不是跟在url后面再膳。

2.2 原則與實(shí)現(xiàn)

1挺勿、接口是可持續(xù)運(yùn)營(yíng)的,不是上線就完事了喂柒,要能夠持續(xù)的版本更新迭代
2不瓶、向下兼容:如果一個(gè)功能以API的方式公布出來(lái),那么在發(fā)布以后灾杰,它的對(duì)外接口就已經(jīng)固定蚊丐,不能取消。接口簽名的改動(dòng)對(duì)調(diào)用方會(huì)造成非常大的影響
3艳吠、接口最好實(shí)現(xiàn)冪等性麦备。什么意思是,查詢是冪等的昭娩,無(wú)論查多少次凛篙,都不會(huì)對(duì)應(yīng)用數(shù)據(jù)造成影響。實(shí)現(xiàn)冪等性有什么意義呢栏渺?眾所周知呛梆,網(wǎng)絡(luò)是不穩(wěn)定了,調(diào)用者發(fā)生重試的時(shí)候磕诊,還能不能保持正確的數(shù)據(jù)狀態(tài)填物?CUD操作就不是冪等的。要實(shí)現(xiàn)冪等性要付出一定代價(jià)霎终,比如借助額外的token參數(shù)校驗(yàn)融痛,或者校驗(yàn)orderid、userid神僵、時(shí)間戳等參數(shù)來(lái)實(shí)現(xiàn)冪等操作雁刷。
4、接口功能應(yīng)單一化保礼,不能有歧義沛励,單一職責(zé)责语。
5、服務(wù)是無(wú)狀態(tài)的目派,不保存session信息坤候,不依賴于session或cookie。有狀態(tài)的服務(wù)難以使用與運(yùn)維企蹭。

2.3 接口簽名

什么叫簽名白筹,其實(shí)無(wú)非就是方法名、輸入谅摄、輸出參數(shù)徒河。
如何設(shè)計(jì)簡(jiǎn)單易用的接口呢?

鑒于PUT DELETE在網(wǎng)絡(luò)中可能被防火墻屏蔽送漠,在互聯(lián)網(wǎng)環(huán)境中不夠友好顽照,因此全部使用post來(lái)代替,舉例:

GET /user/getlist?condition=xxx:返回對(duì)象的列表
GET /user/getuser?id=xxx:返回單個(gè)對(duì)象
POST /user/create 創(chuàng)建
POST /user/update 修改
POST /users/delete?id=xxx:刪除

1闽寡、返回統(tǒng)一的數(shù)據(jù)格式ResultBean代兵,考慮異常返回
2、參數(shù)中不能出現(xiàn) jsonstring map之類的復(fù)雜參數(shù)
3爷狈、create應(yīng)該返回新對(duì)象的id標(biāo)識(shí)
4植影、Controller做參數(shù)格式的轉(zhuǎn)換,不允許把json涎永,map這類對(duì)象傳到services去思币,也不允許services返回json、map
5土辩、參數(shù)中一般情況不允許出現(xiàn)Request,Response這些對(duì)象
6抢野、異常處理:業(yè)務(wù)異常拷淘、系統(tǒng)異常
controller一般不吃掉異常,拋出Businessexception指孤,統(tǒng)一交給AOP攔截器進(jìn)行最后的處理
7启涯、后臺(tái)異常一定要有通知機(jī)制

2.4 接口代碼示例

/**
* <p>
    * </p>
*
* @author wuzuquan
* @date 2018-06-28 09:09:00
* @version
*/
@RestController
@RequestMapping(value = "/tbempdata")
@ApiVersion(1)
public class TbEmpDataController {

    Logger logger = LoggerFactory.getLogger(this.getClass());
    @Autowired
    private HttpServletRequest request;
    @Autowired
    private HttpServletResponse response;
    @Autowired
    private TbEmpDataMapper mapper;


    @ApiOperation(value="獲取單條記錄", notes="根據(jù)url的id來(lái)獲取詳細(xì)信息")
    @RequestMapping(value = "/get",method = RequestMethod.GET)
    public ResultBean<TbEmpData> get(String id){
        TbEmpData item=  mapper.selectByPrimaryKey(id);
        if(item!=null){
            return new ResultBean<TbEmpData>(item);
        }else {
            return new ResultBean<TbEmpData>(ExceptionEnum.RESOURCE_NOT_FOUND,null,"找不到該記錄",null);
        }
    }


    @RequestMapping(value = "/getlist",method = RequestMethod.GET)
    public ResultBean<List<TbEmpData>> getList(){
        List<TbEmpData> list=  mapper.selectAll();
        ResultBean<List<TbEmpData>> resultBean=new ResultBean<List<TbEmpData>>(list);
        return  resultBean;
    }

    @RequestMapping(value = "/create",method = RequestMethod.POST)
    public ResultBean<String> create(@Validated TbEmpData item){
        int  result= mapper.insert(item);
        logger.info("create TbEmpData success,record,{}"+ JsonUtil.bean2Json(item));
        ResultBean<String> resultBean=new ResultBean<String>("");
        return  resultBean;
    }

    @RequestMapping(value = "/update",method = RequestMethod.POST)
    public ResultBean<String> update(@Validated TbEmpData item){
        int  result=  mapper.updateByPrimaryKey(item);
        logger.info("update TbEmpData success,record,{}"+ JsonUtil.bean2Json(item));
        ResultBean<String> resultBean=new ResultBean<String>("");
        return  resultBean;
    }

    @RequestMapping(value = "/deleteByID",method = RequestMethod.POST)
    public ResultBean<Integer> delete(String id){
        int  result=  mapper.deleteByPrimaryKey(id);
        logger.info("delete TbEmpData success,record id,{}"+ id);
        ResultBean<Integer> resultBean=new ResultBean<Integer>(result);
        return  resultBean;
    }

    @RequestMapping(value = "/delete",method = RequestMethod.POST)
    public ResultBean<Integer> delete(TbEmpData item){
        int  result=  mapper.updateByPrimaryKey(item);
        ResultBean<Integer> resultBean=new ResultBean<Integer>(result);
        return  resultBean;
    }

}

2.5 返回的數(shù)據(jù)結(jié)構(gòu)ResultBean

不能只考慮正常執(zhí)行返回結(jié)果,還有考慮各種業(yè)務(wù)異常恃轩、系統(tǒng)異常结洼、如何給用戶友好的錯(cuò)誤提示等等。

public class  ResultBean<T> implements Serializable{

    private int code=ExceptionEnum.SUCCESS.getCode();
    /**
     * 編號(hào)
     */
    private String errStr;
    //= ExceptionEnum.SUCCESS.toString();

    /**
     * 文本信息
     */
    private String message="success";

    /**
     數(shù)據(jù)內(nèi)容
     */
    private  T data;

1叉跛、code字段描述了本次請(qǐng)求的狀態(tài)松忍,

SUCCESS(200),
    RESOURCE_NOT_FOUND(404),
    ARGUMENTS_INVALID(401),
    BUSINESS_ERROR(400),
    SERVER_ERROR(500);

2、errStr代表業(yè)務(wù)異常編碼筷厘,只有code為business_error時(shí)才需要填充此字段
3鸣峭、message 業(yè)務(wù)異常對(duì)應(yīng)的文字描述
4宏所、data 真實(shí)的業(yè)務(wù)數(shù)據(jù),泛型

2.6 參數(shù)校驗(yàn)

統(tǒng)一的參數(shù)校驗(yàn)摊溶,使用hibernate validator組件爬骤,在controller層統(tǒng)一處理

  @RequestMapping(value = "/create",method = RequestMethod.POST)
    public ResultBean<String> create(@Validated TbEmpData item){
        int  result= mapper.insert(item);
        logger.info("create TbEmpData success,record,{}"+ JsonUtil.bean2Json(item));
        ResultBean<String> resultBean=new ResultBean<String>("");
        return  resultBean;
    }

對(duì)需要校驗(yàn)的參數(shù)添加validated注解。springmvc校驗(yàn)失敗時(shí)會(huì)拋出bindexception莫换,統(tǒng)一在攔截器里處理此異常

@ControllerAdvice(annotations = RestController.class)
@ResponseBody
public class CommonExceptionHandler {
    /**
     * logback new instance
     */
    Logger logger = LoggerFactory.getLogger(this.getClass());

    /**
     * 統(tǒng)一處理bean驗(yàn)證拋出的參數(shù)校驗(yàn)異常
     * 參數(shù)校驗(yàn)失敗霞玄,統(tǒng)一采用warn記錄日志
     * @see javax.validation.Valid
     * @see org.springframework.validation.Validator
     * @see org.springframework.validation.DataBinder
     */
    @ExceptionHandler(BindException.class)
    public ResultBean<List<FieldError>> validExceptionHandler(BindException e, WebRequest request, HttpServletResponse response) {

        logger.warn("參數(shù)校驗(yàn)失敗,{}", JsonUtil.bean2Json(e.getTarget()));
        List<FieldError> fieldErrors=e.getBindingResult().getFieldErrors();

        return  new ResultBean<>(ExceptionEnum.ARGUMENTS_INVALID,null,"arguments invalid",fieldErrors);

    }

2.7 異常處理

程序不可能按照人的意志,永遠(yuǎn)完美的運(yùn)行下去拉岁,總會(huì)出點(diǎn)毛病坷剧,出點(diǎn)bug。良好的異常處理也是一個(gè)攻城獅必備的能力膛薛。
1听隐、影響業(yè)務(wù)運(yùn)行的異常:業(yè)務(wù)層代碼出現(xiàn)異常要么不處理,要么捕獲處理拋再出業(yè)務(wù)異常
2哄啄、不影響正常邏輯的異常雅任,可直接吃掉
3、對(duì)業(yè)務(wù)異常進(jìn)行適當(dāng)?shù)漠惓>幋a咨跌,詳細(xì)代碼參考core模塊下的exception
4沪么、國(guó)際化文本提示:對(duì)每個(gè)業(yè)務(wù)異常編碼,將對(duì)應(yīng)的文本提示信息寫入國(guó)際化資源文件
5锌半、最終在異常攔截器里處理參數(shù)校驗(yàn)異常禽车、業(yè)務(wù)異常、未捕獲的系統(tǒng)異常

/**
     * 統(tǒng)一處理bean驗(yàn)證拋出的參數(shù)校驗(yàn)異常
     * 參數(shù)校驗(yàn)失敗刊殉,統(tǒng)一采用warn記錄日志
     * @see javax.validation.Valid
     * @see org.springframework.validation.Validator
     * @see org.springframework.validation.DataBinder
     */
    @ExceptionHandler(BindException.class)
    public ResultBean<List<FieldError>> validExceptionHandler(BindException e, WebRequest request, HttpServletResponse response) {

        logger.warn("參數(shù)校驗(yàn)失敗,{}", JsonUtil.bean2Json(e.getTarget()));
        List<FieldError> fieldErrors=e.getBindingResult().getFieldErrors();

        return  new ResultBean<>(ExceptionEnum.ARGUMENTS_INVALID,null,"arguments invalid",fieldErrors);

    }


    /**
     * 統(tǒng)一攔截處理業(yè)務(wù)異常
     */
    @ExceptionHandler(BusinessException.class)
    public ResultBean<String> validExceptionHandler(BusinessException e) {
        logger.warn("業(yè)務(wù)異常:【{}】", e.getMessage(),e);
        ResultBean<String> result=new ResultBean<String>();
        result.setCode(ExceptionEnum.BUSINESS_ERROR.getCode());
        result.setErrStr(e.getErrCode());
        result.setMessage(e.getMessage());
        result.setData(JsonUtil.bean2Json(e.getData()));
        return result;
    }

    /**
     * 默認(rèn)統(tǒng)一異常處理方法
     * @param e 默認(rèn)Exception異常對(duì)象
     * @return
     */
    @ExceptionHandler
    @ResponseStatus
    public ResultBean<String> runtimeExceptionHandler(Exception e) {
        logger.error("運(yùn)行時(shí)異常:【{}】", e.getMessage(),e);
        ResultBean<String> result=new ResultBean<String>();
        result.setCode(ExceptionEnum.SERVER_ERROR.getCode());
        result.setMessage(e.getMessage()+"-- traceid:"+ MDC.get("traceId"));
        return result;
    }

2.8 記錄日志

推薦使用log4j2 或logback+kafka+elk來(lái)建立日志體系殉摔。

日志要求:
1、能定位到機(jī)器IP
2记焊、定位到用戶干了啥逸月,用戶ID
3、修改新增操作必須打印日志
4遍膜、重要參數(shù)必須打印參數(shù)值
5碗硬、日志記錄的內(nèi)容,不允許使用字符串拼接++ 瓢颅,浪費(fèi)資源
6恩尾、推業(yè)務(wù)消息,必須記錄返回值挽懦,便于跟蹤

2.9 返回多樣性的數(shù)據(jù)格式

服務(wù)提供者要支持常用的json翰意、xml、protobuf,根據(jù)請(qǐng)求者發(fā)送的httpheader中accept字段猎物,返回對(duì)應(yīng)的數(shù)據(jù)格式虎囚。springmvc采用管道過(guò)濾器來(lái)處理,在http處理鏈上注冊(cè)多個(gè)處理器蔫磨,層層攔截淘讥,符合條件就會(huì)被處理。
當(dāng)然了要設(shè)置一個(gè)默認(rèn)值堤如,默認(rèn)json蒲列,json處理器掛在第一個(gè)位置,既是默認(rèn)值搀罢。
本方案采用protostuff來(lái)支持protobuf數(shù)據(jù)格式蝗岖,并非使用google提供的jar包,無(wú)須每個(gè)對(duì)象都寫個(gè).proto文件榔至,這操作也是反人類抵赢。

在webconfig中添加如下代碼,具體參考代碼工程:https://github.com/wuzuquan/microservice

    //添加protobuf支持唧取,需要client指定accept-type:application/x-protobuf
    @Override
    public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        StringHttpMessageConverter stringConverter = new StringHttpMessageConverter();
        stringConverter.setDefaultCharset(Charset.forName("utf-8"));
        List<MediaType> list = new ArrayList<MediaType>();
        list.add(MediaType.TEXT_PLAIN);
        stringConverter.setSupportedMediaTypes(list);
        MappingJackson2XmlHttpMessageConverter xmlConverter=new MappingJackson2XmlHttpMessageConverter();
        xmlConverter.setDefaultCharset(Charset.forName("utf-8"));
        List<MediaType> list2 = new ArrayList<MediaType>();
        list2.add(MediaType.APPLICATION_XML);
        xmlConverter.setSupportedMediaTypes(list2);
        converters.add(0,stringConverter);
        converters.add(0,xmlConverter);
        converters.add(0,new ProtostuffHttpMessageConverter());
        converters.add(0,getCustomJacksonConverter(objectMapper));
    }

不建議在controller方法上寫死返回的數(shù)據(jù)格式铅鲤,幫倒忙。

2.10 API接口文檔--swagger

服務(wù)是要公布給其他開發(fā)者調(diào)用的枫弟,怎么跟別人描述你提供了多少服務(wù)邢享,怎么調(diào)用,注意事項(xiàng)是什么淡诗?
傳統(tǒng)的做法是寫個(gè)word文檔骇塘,洋洋灑灑幾百頁(yè)。實(shí)際上沒(méi)什么卵用:
1韩容、且不說(shuō)寫這么個(gè)文檔要耗費(fèi)多大的精力款违,文檔放在哪,便于大家調(diào)閱都是個(gè)問(wèn)題群凶。
2插爹、試問(wèn)誰(shuí)有耐心去看這么個(gè)冗長(zhǎng)的API文檔?
3座掘、開發(fā)者怎么進(jìn)行調(diào)試測(cè)試递惋?
4柔滔、服務(wù)更新怎么辦溢陪,還得去同步修改word文檔,一次兩次可以睛廊,N次呢形真,你會(huì)不會(huì)自亂陣腳?有人說(shuō),通過(guò)管理手段來(lái)保證咆霜,保證個(gè)蛋蛋哪邓馒,反人類思維。

是時(shí)候該swagger出場(chǎng)了蛾坯。

原理很簡(jiǎn)單光酣,通過(guò)掃描controller包下的接口類,對(duì)每個(gè)類脉课、每個(gè)方法救军、輸出輸出參數(shù)進(jìn)行解析,自動(dòng)化生成友好的API文檔倘零,也可以在線調(diào)試測(cè)試唱遭。保證文檔與代碼是實(shí)時(shí)統(tǒng)一的。

2.11 接口版本

在類呈驶、方法上添加注解@ApiVersion(1)拷泽,最終版本號(hào)呈現(xiàn)在url中

2.12 冪等性

由于宕機(jī),網(wǎng)絡(luò)抖動(dòng)袖瞻,超時(shí)等各種異常情況司致,還參與分布式事務(wù),我們通常會(huì)有重試機(jī)制來(lái)保證高可用虏辫。這就要求我們的服務(wù)對(duì)同一個(gè)請(qǐng)求的多次重試蚌吸,依然能正確響應(yīng)。
講那么多廢話砌庄,最關(guān)鍵的一點(diǎn)就是業(yè)務(wù)邏輯實(shí)現(xiàn)去重羹唠,可以借助redis db 等進(jìn)行去重,返回正確的數(shù)據(jù)娄昆。

3 服務(wù)調(diào)用者最佳實(shí)踐--打通最后一公里

3.1 調(diào)試測(cè)試

通常情況下佩微,使用服務(wù)提供者發(fā)布的swagger API在線文檔即可進(jìn)行調(diào)試測(cè)試。
復(fù)雜點(diǎn)的萌焰,比如要設(shè)置httpheader信息哺眯,則可通過(guò)postman之類的http調(diào)試工具進(jìn)行。
在此推薦一個(gè)chrome插件 https://chrome.google.com/webstore/detail/restlet-client-rest-api-t/aejoelaoggembcahagimdiliamlcdmfm

3.2 提升穩(wěn)定性與性能--使用okhttp

okhttp在穩(wěn)定性扒俯、連接池方面處理的很好奶卓,推薦使用。在springboot工程中撼玄,推薦結(jié)合resttemplate使用夺姑。

 @Bean
    public OkHttpClient okHttpClient() {
        //注意:只有明確知道服務(wù)端支持H2C協(xié)議的時(shí)候才能使用。添加H2C支持掌猛,
        OkHttpClient.Builder builder = new OkHttpClient.Builder();
      // .protocols(Collections.singletonList(Protocol.H2_PRIOR_KNOWLEDGE));
        Dispatcher dispatcher=new Dispatcher(
                httpTracing.tracing().currentTraceContext()
                        .executorService(new Dispatcher().executorService())
        );
        //設(shè)置連接池大小
        dispatcher.setMaxRequests(1000);
        dispatcher.setMaxRequestsPerHost(200);
       ConnectionPool pool = new ConnectionPool(20, 30, TimeUnit.MINUTES);


        builder.connectTimeout(2000, TimeUnit.MILLISECONDS)
                .readTimeout(10, TimeUnit.SECONDS)
                .writeTimeout(10, TimeUnit.SECONDS)
                .connectionPool(pool)

                .dispatcher(dispatcher)
               //鏈路監(jiān)控埋點(diǎn)
                .addNetworkInterceptor(TracingInterceptor.create(httpTracing))
                //.addInterceptor(new OkHttpInterceptor())
                .retryOnConnectionFailure(true);
        return builder.build();
    }

詳細(xì)配置代碼參看HttpClientConfig盏浙,自行DIY。

3.3 提升性能--我要protobuf

protobu具備體積小、高性能等特性废膘,如果服務(wù)提供者支持protobuf格式竹海,可使用此數(shù)據(jù)格式來(lái)交互。

 // 把自定義的ClientHttpRequestInterceptor添加到RestTemplate丐黄,可添加多個(gè)
        restTemplate.setInterceptors(Collections.singletonList(new ProtobufHeaderInterceptor()));

public class ProtobufHeaderInterceptor implements ClientHttpRequestInterceptor {

    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
            throws IOException {
        HttpHeaders headers = request.getHeaders();
        // 加入自定義字段
        headers.clear();
        headers.add("Accept","application/x-protobuf");
        // 保證請(qǐng)求繼續(xù)被執(zhí)行
        return execution.execute(request, body);
    }
}

通過(guò)給resttemplate設(shè)置攔截器斋配,所有http請(qǐng)求統(tǒng)一添加頭信息。

3. 4提升可用性--重試機(jī)制

如果服務(wù)出現(xiàn)不可用灌闺、或者網(wǎng)絡(luò)抖動(dòng)怎么辦许起?
重試啊,重試賊好用菩鲜。
如果有結(jié)合ribbon客戶端負(fù)載工具园细,直接配載ribbon重試策略。
如果未使用ribbon接校,okhttp也支持配置重試策略猛频。

3.5 代碼示例

@Qualifier("signleTemplate")
    @Autowired
    private RestTemplate restTemplate;

@Test
    public  void testListService()  throws Exception {
        String url = "http://localhost:8080/v1/organ/getlist?organCode=10.230";
        ParameterizedTypeReference<ResultBean<List<A1001>>> typeRef = new ParameterizedTypeReference<ResultBean<List<A1001>>>() {};

        ResponseEntity<ResultBean<List<A1001>>> responseEntity = restTemplate.exchange(
                url, HttpMethod.GET,null , typeRef);
        ResultBean<List<A1001>> myModelClasses = responseEntity.getBody();

        Assert.assertEquals(myModelClasses.getData().get(0).getOrganCode(),"10.230");

    }

4 服務(wù)治理

4.1 認(rèn)證、鑒權(quán)蛛勉、限流鹿寻、日志

把這些通用處理剝離處理不要每個(gè)服務(wù)提供者都去實(shí)現(xiàn)一遍,統(tǒng)一交給API網(wǎng)關(guān)處理诽凌。具體不展開毡熏。

4.2 服務(wù)版本更新

版本更新、服務(wù)上下線侣诵,一定要管理痢法,該管的一定要管。
避免對(duì)下游業(yè)務(wù)造成不良影響杜顺。

5 不要濫用服務(wù)

服務(wù)是有成本的财搁,不要萬(wàn)事皆服務(wù)。

服務(wù)是可重用的躬络、可沉淀的尖奔、持續(xù)運(yùn)營(yíng)。

6 題外話:當(dāng)一只高效的程序猿

懶人改變世界穷当,不要盲目加班提茁,平時(shí)注重自身技術(shù)積累沉淀,養(yǎng)成良好的開發(fā)習(xí)慣馁菜,提升軟件質(zhì)量茴扁。

具備良好習(xí)慣的程序猿頂4個(gè)碼農(nóng),大家不要做真正的碼農(nóng)火邓、純體力活丹弱。機(jī)會(huì)總是留給有準(zhǔn)備的人的。

何必天天加班排查故障铲咨、找bug躲胳、殺連接。纤勒。坯苹。自我麻痹、毫無(wú)長(zhǎng)進(jìn)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末摇天,一起剝皮案震驚了整個(gè)濱河市粹湃,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌泉坐,老刑警劉巖为鳄,帶你破解...
    沈念sama閱讀 212,816評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異腕让,居然都是意外死亡孤钦,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門纯丸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)偏形,“玉大人,你說(shuō)我怎么就攤上這事觉鼻】∨ぃ” “怎么了?”我有些...
    開封第一講書人閱讀 158,300評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵坠陈,是天一觀的道長(zhǎng)萨惑。 經(jīng)常有香客問(wèn)我,道長(zhǎng)仇矾,這世上最難降的妖魔是什么咒钟? 我笑而不...
    開封第一講書人閱讀 56,780評(píng)論 1 285
  • 正文 為了忘掉前任,我火速辦了婚禮若未,結(jié)果婚禮上朱嘴,老公的妹妹穿的比我還像新娘。我一直安慰自己粗合,他們只是感情好萍嬉,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,890評(píng)論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著隙疚,像睡著了一般壤追。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上供屉,一...
    開封第一講書人閱讀 50,084評(píng)論 1 291
  • 那天行冰,我揣著相機(jī)與錄音溺蕉,去河邊找鬼。 笑死悼做,一個(gè)胖子當(dāng)著我的面吹牛疯特,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播肛走,決...
    沈念sama閱讀 39,151評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼漓雅,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了朽色?” 一聲冷哼從身側(cè)響起邻吞,我...
    開封第一講書人閱讀 37,912評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎葫男,沒(méi)想到半個(gè)月后抱冷,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,355評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡梢褐,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,666評(píng)論 2 327
  • 正文 我和宋清朗相戀三年徘层,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片利职。...
    茶點(diǎn)故事閱讀 38,809評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡趣效,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出猪贪,到底是詐尸還是另有隱情跷敬,我是刑警寧澤,帶...
    沈念sama閱讀 34,504評(píng)論 4 334
  • 正文 年R本政府宣布热押,位于F島的核電站西傀,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏桶癣。R本人自食惡果不足惜拥褂,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,150評(píng)論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望牙寞。 院中可真熱鬧饺鹃,春花似錦、人聲如沸间雀。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)惹挟。三九已至茄螃,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間连锯,已是汗流浹背归苍。 一陣腳步聲響...
    開封第一講書人閱讀 32,121評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工用狱, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人拼弃。 一個(gè)月前我還...
    沈念sama閱讀 46,628評(píng)論 2 362
  • 正文 我出身青樓夏伊,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親肴敛。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,724評(píng)論 2 351

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理吗购,服務(wù)發(fā)現(xiàn)医男,斷路器,智...
    卡卡羅2017閱讀 134,638評(píng)論 18 139
  • 關(guān)于Mongodb的全面總結(jié) MongoDB的內(nèi)部構(gòu)造《MongoDB The Definitive Guide》...
    中v中閱讀 31,914評(píng)論 2 89
  • 前言 本開發(fā)規(guī)范基于《阿里巴巴Java開發(fā)手冊(cè)終極版》修改捻勉,并集成我們自己的項(xiàng)目開發(fā)規(guī)范镀梭,整合而成。 為表示對(duì)阿里...
    4ea0af17fd67閱讀 5,634評(píng)論 0 5
  • 又是一天踱启,除了窗口顏色的變化报账,鏡子里面更加粗糙的皮膚,溫暖的暖氣和被窩還是給了他一些力氣埠偿。想想已經(jīng)是三年了透罢,三年前...
    閑置的魚閱讀 167評(píng)論 0 1
  • 昨天有一篇首頁(yè)推薦文章,題為:《簡(jiǎn)書是個(gè)好平臺(tái)》冠蒋。 我剛接觸簡(jiǎn)書短短幾天羽圃,也深有同感。在我看來(lái)抖剿,除了那篇作者M(jìn)IC...
    故鄉(xiāng)圓月明閱讀 314評(píng)論 1 4