首页 > 其他 > 详细

系统开发中的坑

时间:2016-03-15 02:11:46      阅读:198      评论:0      收藏:0      [点我收藏+]

系统开发的坑

?

一.幂等性

二.数据库

三.代码默认写法

四.业务设计

?

一、幂等性

不管调用多少次,都应该产生一样的效果和返回一样的结果

案例:

? 前端重复提交选中的数据,应该后台只产生对应这个数据的一个反应

结果。

? 我们发起一笔付款请求,应该只扣用户账户一次钱,当遇到网络重发

或系统bug重发,也应该只扣一次钱

? 发送消息,也应该只发一次,同样的短信发给用户,用户会哭的

? 创建业务订单,一次业务请求只能创建一个,创建多个就会出大问题。

概念

查询操作

查询一次和查询多次,在数据不变的情况下,查询结果是一样的。

select是天然的幂等操作 。

?

技术实现(一)

删除操作

删除操作也是天生幂等的,删除一次和多次删除都是把数据删除。

(注意可能返回结果不一样,删除的数据不存在,返回0,删除的数据多条,

返回结果多个)

Select * from table_xxx where id=#id;

You-?delete

不抛出异常,删除操作都返回true, 注意Dao层代码写法

?

技术实现(二)

唯一索引,防止新增脏数据

案例:

支付宝的资金账户,支付宝也有用户账户,每个用户只能有一个资金账

户,怎么防止给用户创建资金账户多个,那么给资金账户表中的用户ID加

唯一索引,所以一个用户新增成功一个资金账户记录

要点:

唯一索引或唯一组合索引来防止新增数据存在脏数据

(当表存在唯一索引,并发新增报错时,再次调用接口会返回成功结果)

注意点:

先查询,用唯一索引来查,后新增,新增控制幂等

?

技术实现(三)

token机制,防止页面重复提交

业务要求:

页面的数据只能被点击提交一次

发生原因:

由于重复点击或者网络重发,或者nginx重发等情况会导致数据被重复提交

解决办法:

集群环境:采用token加redis(redis单线程的,处理需要排队)

单JVM环境:采用token加redis或token加jvm内存

处理流程:

1. 数据提交前要向服务的申请token,token放到redis或jvm内存,token有效时间

2. 提交后后台校验token,同时删除token,生成新的token返回

token特点:

要申请,一次有效性,可以限流

注意:redis要用删除操作来判断token,删除成功代表token校验通过,如

果用select+delete来校验token,存在并发问题,不建议使用

?

技术实现(四)

悲观锁

获取数据的时候加锁获取 ,进行业务操作在提

select * from table_xxx where id = ‘xxx‘ for update;

建议使用悲观锁: 账户一个人拥有, 铜宝账户,积分账户,会员等级账户

不建议使用:产品份额账户。。。几百个人抢一个份额

注意:id字段一定是主键或者唯一索引,不然是锁表,会死人的

悲观锁使用时一般伴随事务一起使用,数据锁定时间可能会很长,根据实际情况

选用, 数据库引擎要用Innodb,支持行锁

?

技术实现(五)

乐观锁

乐观锁只是在更新数据那一刻锁表,其他时间不锁表,所以相对于悲观锁,效率更高。

乐观锁的实现方式多种多样可以通过version或者其他状态条件:

1. 通过版本号实现

update table_xxx set name=#name#,version=version+1 where version=#version#

2. 通过条件限制

update table_xxx set avai_amount=avai_amount-#subAmount# where avai_amount-

#subAmount# >= 0

增加额外表来保证单条记录的幂等,一个用户一个幂等。

要求: avai_amount-#subAmount# >= 0 ,这个情景适合不用版本号,只更新是做数据安

全校验,适合库存模型,扣份额和回滚份额,性能更高

注意:乐观锁的更新操作,最好用主键或者唯一索引来更新,这样是行锁,否则更新时会锁

表,上面两个sql改成下面的两个更好

update table_xxx set name=#name#,version=version+1 where id=#id# and

version=#version#

update table_xxx set avai_amount=avai_amount-#subAmount# where id=#id# and

avai_amount-#subAmount# >= 0

?

技术实现(六)

分布式锁, 拿不到conn

还是拿插入数据的例子,如果是分布是系统,构建全局唯一索引比较困

难,例如唯一性的字段没法确定,这时候可以引入分布式锁,通过第三

方的系统(redis或zookeeper),在业务系统插入数据或者更新数据,获

取分布式锁,然后做操作,之后释放锁,这样其实是把多线程并发的锁

的思路,引入多多个系统,也就是分布式系统中得解决思路。

要点:某个长流程处理过程要求不能并发执行,可以在流程执行之前根

据某个标志(用户ID+后缀等)获取分布式锁,其他流程执行时获取锁就会

失败,也就是同一时间该流程只能有一个能执行成功,执行完成后,释

放分布式锁(分布式锁要第三方系统提供)

?

技术实现(七)

select + insert

并发不高的后台系统,或者一些任务JOB,为了支持幂等,支持重复执行,简单

的处理方法是,先查询下一些关键数据,判断是否已经执行过,在进行业务处理,

就可以了

注意:核心高并发流程不要用这种方法 ,每天只执行几次的任务,建议使用,

性能好一点

?

技术实现(八)

状态机幂等

在设计单据相关的业务,或者是任务相关的业务,肯定会涉及到状态机

(状态变更图),就是业务单据上面有个状态,状态在不同的情况下会发

生变更,一般情况下存在有限状态机,这时候,如果状态机已经处于下

一个状态,这时候来了一个上一个状态的变更,理论上是不能够变更的,

这样的话,保证了有限状态机的幂等。

注意:订单等单据类业务,存在很长的状态流转,一定要深刻理解状态

机,对业务系统设计能力提高有很大帮助

?

技术实现(九)

对外提供接口的api如何保证幂等

如银联提供的付款接口:需要接入商户提交付款请求时附带:source来源,seq序

列号

source+seq在数据库里面做唯一索引,防止多次付款,(并发时,只能处理一个请

求)

重点:

对外提供接口为了支持幂等调用,接口有两个字段必须传,一个是来源source,

一个是来源方序列号seq,这个两个字段在提供方系统里面做联合唯一索引,这

样当第三方调用时,先在本方系统里面查询一下,是否已经处理过,返回相应处

理结果;没有处理过,进行相应处理,返回结果。注意,为了幂等友好,一定要

先查询一下,是否处理过该笔业务,不查询直接插入业务系统,会报错,但实际

已经处理了。

?

技术实现(十)

?

?

二、数据库

? 字段的命名规范化,简洁易懂, cash—取现,不合理,withdraw合理些

? 字段名要有注释,方便后人(不是死了的)查找问题

? 主键是选择自增的方式,还是带业务含义的主键,还是一个自增方式主键

+ 一个业务含义流水要考虑清楚

? 字段长度选择,字符串考虑未来扩展性问题,整形有很多种,int(11)和

int(2),在性能上并没有优化

? 数据库引擎选择innodb

? 是否有幂等性业务需求靠数据库唯一索引来完成,索引设置的合理性

? Decimal的长度选择,保留小数点后6位

? 同一个系统中各个表中同样含义的字段,名词要一致hk_detail,,HkDetail

? 同一个系统前缀一致,tb_share,tb_income_expense, tbShareId,

shareId其他表里面存储的(MBA智库百科,查询金融词汇)

数据库设计原则

每个表都应该设置一个ID主键,最好的是一个INT型,并且设置上自动增

加的AUTO_INCREMENT标志,这点其实应该作为设计表结构的第一件必

然要做的事!!

个人强烈建议:自增id主键+全局唯一序列号

100xxxxxx-广东省, 200xxxxxx-黑龙江

遍历需求的对于自增ID的表来说很容易实现

?

主键设计:

1. 自增

2. 15位时间戳+业务标志+rand 32

3. 分布式全局唯一, UUID或额外系统主键生成系统支持

主键的设计

类型 占用字节 范围

tinyint 1 -128~127

smallint 2 -32768~32767

mediumint 3 -8388608~8388607)

int 4 -2147483648~2147483647

bigint 8 +-9.22*10的18次方

?

数据库字段长度的优化

? 原则:

? 不浪费空间, 能用小的数据类型干嘛占用那么多空间

? 方便以后扩容

? 能用int,不影响业务理解,不用char

? Tinyint(30)------?30个字节---? 10, 000000000000000000010,

? 例子

? direction收支方向, 就收和支两个值,一定用tinyint(1)

? 原则:

?同一个应用都相同前缀,如tbj_aaa,tbj_bbb

?简洁,见名知意

?

数据库表名的命名

? 原则:

?同一个含义的字段各个表中名称一致

?尽量用专业词汇

?简洁,见名知意

? 例子

? 持仓的专业词汇是position,份额是share,所以份额表

示tb_share

? 用户份额id,没有用tb_share_id而是用share_id,简洁

易懂

?

数据库字段的命名

? 原则:

?根据业务需求设计索引

?索引不要过多,影响插入更新性能

?如果有幂等性需求,设置数据库唯一索引

? 例子

? 如铜板账户,userId设置为唯一索引,一个人只能有一

个铜板账户

? 收支流水表的userID设置普通索,查询效率高

? Create_time 默认加索引。。。。

? A,b,c 组合索引,, A**,ABX XXB XXC, a+b+c, 按位

加索引

?

设计索引

? 原则:

?整形效率高于字符串,高于datetime

?时间long形式时间建议加

?日期:int 20121212, 2100000 2010000

?长度过程用text, remark,varchar1024, 默认截取,

前端, append, /n

?如果表数据量很大,主键用bigint

? 例子

? occur_date用整形不用datetime,20121212,查询频繁用整

形效率高

? 因为每个表未来的数据量都会很大,用bigint

?

字段类型的选择

? 原则:

?全表扫描会造成数据库挂掉,OOM

?索引值很少,走索引也会全表扫描

?删数据时,加limit限制和where后必须有条件

? 例子

? DAO层面限制不走索引的查询sql

? DAO层面限制索引值少的查询sql

? Delete from tb_position where id=142343 limit 1;

如何避免全表扫描,如何避免误删数据

?

深入理解业务,根据业务特性来分表,根据

查询需求来分表

案例:

1. 代金券分表 (季度分)

2. 每日收益分表 (userId取余)

3. 对账分表 (按月分)

4. 微信红包分表 (年度+用户分)

怎么分表?

前端要配合改,尽量不支持后台聚合查询

案例:

I. 微信红包查询收支明细, 只能按年查询

II. 按月查询

III.查询历史等

分表后怎么查询?

缓存和DB数据一致

selectById

Redis

数据库

selectById4Update selectListByParam update

Redis

尽量避免在WHERE子句中对字段进行函数或表达式操作,这

将导致引擎放弃使用索引而进行全表扫描。如:

SELECT * FROM T1 WHERE F1/2=100 应改为: SELECT * FROM T1 WHERE

F1=100*2

禁止如下sql:

Select * from table_xxx where nvl(filed_xx)=xxx;(红色字体不能加函数)

索引字段上进行运算会使索引失效。

? 一个xml里不要有两个表名,不做关联表查询,因为mysql接不了那么多客

? 下面的写法不对,不要给字段加函数

Sql的编写

?

?

三、代码默认写法

1. 保留两位有效数字的值,要保留小数点后六位BigDecimal

进行计算

案例:

非标产品收益

2. 保留六位有效数字的值,要保留小数点后20位BigDecimal

进行计算

案例:

铜宝账户

如何保证小数点后计算准确性

每次要new,不 new,原因都查不到

SimpleDateFormat不是线程安全的

如何避免全表扫描和误删数据—DAO层就禁止掉

? 用户ID,异常原因这是必须要记录的,其他的业务关键信息也要记录下来

? 核心业务流程,请求参数,执行完业务影响数据要记录下来,info日志

? 核心业务流程,执行时间要记录下来,info日志

? 一个类中每条日志都要是唯一的,不然你怎么知道是那块代码出来的日志

? 日志要漂亮,要格式化,打印出来的才漂亮

? 案例:

日志是为了查问题,所以怎么记日志?

核心业务系统要有开关,重大异常时可以随时关掉,,服务

降级

案例:

交易系统全局开关,新手体验金开关

出问题了可不可以立即关掉,开关?

一些促销活动和营销活动临时开发,要临时部署,不要影响

主业务系统

案例:

? 支付宝5福

? 15年春晚微信摇一摇

? activity

只用一次的系统,单独开发,独立部署

枚举在增加变量,新老版本,序列化,反序列化中不太友好

经常扩展的常量类用java类不用枚举

? 支持重复执行,幂等,这是基础

F(x), F(Y) 是两个幂等操作: 所以: f(x) +f(y)也是幂等的

? JOB跑一半断电了,支持重跑修复

? JOB返回结果要有,查询多少数据,处理了多少,失败了多少,成功了多少

? JOB执行时间比较久的,继承AbstractJob来写,支持中断, sorce->trade>

? 几个JOB之间应该是互斥关系,可以独立跑,不要搞依赖

JOB什么标准?

? 不要有黄点提示

? 不要用deprecate的方法和类,慢慢改掉

? 特别长的方法名,类名不漂亮

? 代码块是个方块,一行不要超一屏幕,横着拉代码太难看

? 竖着不要超过一屏幕,一个原理

? 嵌套不要超过三层,看不懂

? 一句话解决的,不要写一堆。啰嗦,多用apache工具类

? 代码看着要有结构感觉,不要不换行

? 注释要点睛

代码看着要美丽,漂亮

一些DB的配置数据,dubbo调用的获取的需要多次使用的数

据等

查询一次,放到内存,多次使用,不用每次用到再去查询

一个流程多次用到的数据,提前放内存

? 身份证,姓名,银行卡号,密码等核心数据,存储时要加密存储

看业务需求,哪些字段还需要加密

? 考虑加密算法的性能

RSA < DES < AES < MD5

MD5----》签名----》完整性,正确性

RSA----》加密----》安全性

? 加密算法的安全性

看复杂度选择,一般和性能成反比

敏感信息,加密

改成键值对配置形式,取配置便于维护,配置的选择看情况

去掉过多的if else

一个含义的字段,要用一个英文单词,别多个英文单词一个含义,搞死人的

数据库、应用、前端、含义的一致性

? 有自增主键的遍历

select * from table_xxx where status = #status and limit 1000; id >

#lastQueryId

优点:性能高,不漏数据,遍历数据状态变更不会造成位移

缺点:暂时没发现

? 非自增主键遍历

select * from table_xxx where status = #status and id > #lastQueryId limit

1000;

每次取出最后一条ID,进行下一次查询,性能极高,强哥推荐,好片

是否是带状态更新的遍历:

会可能存在位移差,存在漏数据

解决办法:

1. id>#lastId, limit 100, order没有自增主键

2. Create_time 索引, create_time>=#lastCreateTime, limit 100;支持幂等

3. 存在新增数据的遍历,做临时表,临时表做业务重复判断。

4. create_time 半开查询 create_time >= egtCreateTime and

create_time<ltCreateTime

怎么做遍历?

? 异步怎么做?

? 同步怎么做,超时时间?

? 对方接口的承受能力?

? 容错代码是正常代码的2倍以上?

? doPay,,,,超时了,,,但是它成功了。。。。反查。。。。

容错

? 一眼看不懂,怎么维护

? 一眼看不懂是流程设计问题还是命名问题,反正是有问题的

? 业务领域也是,很清晰,看得懂

一眼看的懂。一眼看不懂,一定有问题

四、业务设计

?复杂简单化

?简单标准化

?标准流程化

?流程自动化

做成一个标准的模式,大家都认为这么做合理,这

事就成了

案例:交易流程,充值流程,做成品牌

怎样做重构?

业务核心点

业务边界点

业务对接点

核心业务状态机

根据状态机设计业务

服务自动升降级

案例:

铜板街交易核心汇总,铜宝系统,卡券,可以挂。挂掉不影响交易

服务升降级

系统开发中的坑

原文:http://825635381.iteye.com/blog/2282909

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!