演讲稿

来源:互联网 发布:crf算法 编辑:程序博客网 时间:2024/05/17 09:05

在项目的应用中,我整理出几个解决问题的点出来讲。如果有更好的方案,可以提一下,或是指出哪里不好,我可以改进。

一、【队列和线程池的运用】

我可以运用队列中的阻塞队列和线程池,根据生产者和消息者的模式做这样一件事,一个程序,运行,然后开出10个线程,等这个程序拿到10线程的运行结果后,再接着运行。

【案例】

比如现在有三个用户发了三条短信到10086,短信A=办理套餐A,短信B=查询流量B,短信C=取消业务C,数据保存在表里(叫上行表),状态是未处理。然后定时器读取上行表这三个短信,根据短信内容(叫指令)办理对应的业务后回复结果,并把状态update为成功或失败

普通:单线程,单个更新数据【运行慢,数据库频繁操作,性能低】

for(){============================>差不多运行15s

if(短信内容==办理套餐A){

运行办理套餐A-后发短信(耗时4s),更新状态1s

}else if(短信内容==查询流量B){

运行查询流量B-后发短信(耗时4s),更新状态1s

}else if(短信内容==取消业务C){

运行取消业务C-后发短信(耗时4s),更新状态1s

}

}

优化:开线程池,单个更新数据【运行快点,但数据库频繁操作,性能低】

for(){============================>差不多运行5s

run(线程个数3){

if(短信内容==办理套餐A){

运行办理套餐A-后发短信(耗时4s),更新状态1s

}else if(短信内容==查询流量B){

运行查询流量B-后发短信(耗时4s),更新状态1s

}else if(短信内容==取消业务C){

运行取消业务C-后发短信(耗时4s),更新状态1s

}

}

}

再优化:开线程池,批量更新数据【运行快点,数据库不用频繁操作,性能好些】

for(){============================>差不多运行4s

run(线程池个数设置为3){

if(短信内容==办理套餐A){

运行办理套餐A(耗时4s)-后发短

}else if(短信内容==查询流量B){

运行查询流量B(耗时4s)-后发短信

}else if(短信内容==取消业务C){

运行取消业务C(耗时4s)-后发短信

}

}

}

主线程等拿到3个子线程的结果后,分类成功的,失败的。只要执行2条update语句批量更新(一条成功类,一条失败类的)

【注:这里我们不是用if判断,为了方便了解才用if讲解,当用户短信数量多时,可以调整每交读数据库条数,调整线程池大小,可以一次读取和写入数据库。方法还可运用在其他地方】

【线程池知识点】(这个可不用看)

1.定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。

Executor executor = Executors.newFixedThreadPool(3);

2.单线程化的线程池,它只会用唯一的工作线程来执行任务,相当于顺序执行各个任务。

Executor executor = Executors.newSingleThreadExecutor();

3.可缓存线程池,线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程。

Executor executor = Executors.newCachedThreadPool();

4.定长线程池,支持定时及周期性任务执行。

【队列知识点】(这个可不用看)

1)add(anObject):如果队列可以容纳,则返回true,否则报异常

2)offer(anObject):如果队列可以容纳,则返回true,否则返回false.

3)put(anObject):队列没有空间,则调用此方法的线程被阻断直到队列里面有空间再继续.

4)poll(time):取走队列里排在首位的对象,若不能立即取出,则可以等time参数规定的时间,取不到时返回null

5)take():取走队列里排在首位的对象,若队列为空,阻断进入等待状态直到Blocking有新的对象被加入为止

ArrayBlockingQueue<Object> queue = new ArrayBlockingQueue<Object>(capacity);

queue.put(onQueueListener.put(attr));//生产者

queue.take()//消息者

二、【性能,效率,安全性考虑】

开发时,我一般会做一些性能,效率,安全性方面的考虑。

【导入导出】

我开发时,把导入导出功能做成一个通过中间件,多个模块有导入导出功能时,只要调用一下就可以用。

导出:

比如数据库有1千万数据要导出,我提供一个模块,给用户做参数配置

1.配置数据库一次分页读取多少条数,【防止一次性读到list后,内存溢出或数据量大数据库读取性能不好】

2.配置每个excel可以有多少个sheet,每个sheet可以放多少行。【防止excel数据过多,打不开】

3.导出时,像数据量很大,按配置参数生成后有excelA.xls,excelB.xls,excelC.xls...等,生成多个excel文件。系统会自动压缩成一个压缩文件,供用户下载。【加压缩后少文件下载大小,也方便一次性下载】

4.一般导出不是做成用户一点击导出,然后程序就运行导出。是做成点击导出后,生成一条记录,待定时器去生成,可以看到导出进度,待文件生成成功,再点击下载【防止数据量大时,一直占用一个http连接数,重复导出,浏览器不小心关掉导出中断,可查看导出记录信息,方便再次下载导出文件,无需再生成等等】

导入:同样的原理,可以上传压缩文件导入,程序自动处理导入的压缩文件,生成一条记录,待定时器去导入,【防止重复导入数据,浏览器中断等。】

【提供第三方接口】

第三方接入时,提供接口方接入方标识A,密钥B,用户名name,密码pwd

这里举个列子:第三方把用户名name,密码pwd,其他请求参数通过密钥B加密后当成一个参数p1,再把接入方标识A当成第2个参数p2;然后请求系统

系统解析:系统根据传过来的标识p2即接入方标识A,找到对应的密钥,解密成功后,可以得到所有参数,再通过用户名和密码检验,成功后返回同样方法加密的密文

【上传图片】

用户通过微信上传图片后,程序自动生成不同规格的图片,根据界面展示合理规格的图片,减少用户流量消耗,打开图片久等问题

【起应急和告警机制】

起应急后,程序设计时,对于比较重要的功能,告知用户“当成系统维护中,”

告警机制,程序定时器扫描哪个功能,当系统失败率高时,通过短信等方式通知维护人员。如果担心短信费用问题,这里可以创建个公众号叫告警群,通过客服消息接口下发通知维护人员。

三、【集群的分布式算法】

这个算法可应用于缓存集群,分库,分表等。

【举例】

memcache类似个map,根据key值,存放缓存map.put(key,value),获取缓存map.get(key)

比如现在有三台memcache0,memcache1,memcache2集群,我要把值为valueA存放到三台中的一台,key值为keyA。后面可以通过keyA,从三台缓存中有valueA的一台取出valueA

普通:随机放入,循环取出【集群个数多,取缓存数据每次都要循环,命中率也低】

1.存放数据到缓存中

因为有三台,所以从0-2随机产生一个数,如果随机数为0,把valueA放到memcache0中memcache0.put(keyA,valueA),如果为2,把valueA放到memcache2中memcache2.put(keyA,valueA),以此类推

2.根据keyA取出valueA

for(int i =0;i<3;i++){//循环集群个数【注,这里为了方便了解才用if判断】

if(i == 0){

Value = memcache0.get(keyA);

}else if(i == 1){

Value = memcache1.get(keyA);

}else if(i == 2){

Value = memcache1.get(keyA);

}

    If(value != null) return value;

}

优化:求余分散【命中率高,但后期添加第4台集群后,命中率低,如果缓存中共4千万数据,大概有:4千万/集群后个数4*添加个数1=1千万数据没命中,得循环】

1.存放数据到缓存中

通过keyA.hashCode()%集群个数3,计算对应哪台,这里是3台,所以余数落在0-2

例如keyA.hashCode()=8,那8%3=2,则把valueA放到memcache2中memcache2.put(keyA,valueA),以此类推

2.根据keyA取出valueA

因为keyA.hashCode()=8,那8%3=2,所以从memcache2中取memcache2.get(keyA)

再优化:顺时针最近节点的方法【命中率高,后期添加第4台集群后,影响范围小】

1.存放数据到缓存中【注,此方法比按0~232 的哈希值方法分布均匀】

先定一个一般集群个数不会超过的数,比如10万,这里是3台,计算出3台memcache的标识分别为0,33333,66666【如图分布】【这里我自己封装了个工具类,可以根据集群个数,自动生成下标,后期添加集群也可以通过工具类自动算出来】:

。通过【keyA.hashCode()%100000(一般集群不过超过10万)】计算,因为keyA.hashCode()=8 ,那8%100000=8,然后8的位置开始顺时针(存放或取)数据,依次为33333memcache1),66666memcache2),0memcache0),最近有一台memcache1,如果保放失败/取不到值,再顺时针最近有一台memcache2,如果还是不行,再继续顺时针找,找到memcache0,以些类推

 

添加第4台,memcache3后,取圆中最长的线段,平均一下,0,33333,66666,83333【如图分布】,影响范围为添加节点到逆时针的一个节点,即虚线部分。【如果缓存中共4千万数据,大概有:4千万/第一次集群个数3/(2的后期所有添加台数1的次方)=6百6十6万数据没命中,影响范围少了,随着集群个添多,分布越均匀】

 

分库分表也可以运用这个算法,如有一个用户表,用户预期3亿,那准备3张表存放,可以根据用户id=keyA,存放valueA=用户信息到这3张表中的1张,后期根据ID更新删除查询时,可根据计算去对应的表操作,

四、【高并发的抢购类活动】

这个是根据2016年支付宝抢红包原理方案做的。

主要原理:

1.例如,一台服务器64G内存,一个tomcat最大连接数是200,现在并发1千个用户请求,单纯用一台服务器一个tomcat,这时有1千-200=800个用户处于等待状态,高并发后,面对短时间内抢购压力,连接数远远不够,造成了一个访问瓶颈。现在采用nginx负载均衡两台服务器,搭建10个tomcat后,相当于拥有200*10=1千个连接数,64*2=128G内存处理量。并发1千个用户请求后,接近无等待请求。消除访问瓶颈。

2.

开始抢购商品,一个表A有个商品iphone记录,id=1,剩余数量num=1千万。抢购开始后,每个用户都要更新这条记录update A set num=num-1 where num >0 ,抢购iphone的用户都要更新这条记录。

 

更新同个记录,一个用户update花0.01s,1万个用户就得花100秒,相当于一个用户点击抢购后,100秒才能运行完,短时间内,数据库更新成了一个瓶颈,而且高并发下,数据库性能急巨下降,数据库连接池也有限,不只100秒。

现在我们采用分库分表,两人个数据库共创建10张表(id=1,剩余数量num=100万,10张表数量之和为1千万,与原来一样),5张表放到数据库A,5张表放到数据库B。1万个用户并发抢购,原本1张表1个库处理,现在改为10张表,2个库处理,相当于开出10个池程同时更新。1万个用户原先花100秒,现在10秒左右运行完成。

【这里采用上面讲的分布式算法来讲解,这里简略说一下。】

用户ID为keyA进来抢购。

keyA.hashCode()=8,那8%10万=8,假定10张表静态缓存ListA={0,1,2,3...9},根据listA顺时针排列后放到listB中,listB=>{8,9,0,1,2,3,4,5,6,7}

boolean update_ok = false; //是否抢购到商品

for(listB){

影响行u = update A(根据标识更新对应表) set num=num-1 where num >0

if(u >0){//更新成功

update_ok = true;

break;

}else{//说明当前表数量为0

ListA.remove(当前表)//方便下一用户知道此表已经没数量,无需再操作

}

}

If(update_ok == true){抢购成功}else{已经抢完}

 

这个是数量扣减,支付宝抢红包是全额扣减,就得多一步碎片合并的机制。当10张表中有一张数量为0,就触发其余数量不为0的张合并。统一到一张表进行抢购。

【这里说一下显示问题,10张表在不断抢购时,要显示总的剩余数量】

采用队列和线程池,主线程开出10个线程分别查询10张剩余数量,等主程序拿到10线程的查询结果后,再将所有数量相加显示。

 

3.通过内存和缓存,把可缓存的数据缓存起来,减少对数据库的操作,提高效率和数据库性能。

 

 

 


0 0