男人插曲女人视频在线

新闻动态
技术中心
技术中心
当前位置:科达自控 >> 服务支持 >> 技术中心 >> 浏览文章
高并发系统方案的优化
作者:黄涛 日期:2020年07月16日 来源:研发部 浏览: 次

内容导读:高并发项目的瓶颈主要在于数据库访问次数上,访问次数越多,对数据库压力也就越大,因此项目中主要也是通过redis进行缓存以及RabbitMQ进行请求入队缓冲等来减少对数据库的访问。

高并发项目的瓶颈主要在于数据库访问次数上,访问次数越多,对数据库压力也就越大,因此项目中主要也是通过redis进行缓存以及RabbitMQ进行请求入队缓冲等来减少对数据库的访问。

一、项目结构搭建

1、集成Thymeleaf

Thymeleaf是一个Java库,是一个XML/XHTML/HTML5模板引擎,能够应用于转换模板文件,以显示应用程序产生的数据和文本。

Thymeleaf旨在提供个优雅的、度可维护的创建模板的式。 为了实现这⼀⽬标,Thymeleaf然模板的概念上,将其逻辑注到模板件中,不会影响模板设计原型。 这改善了设计的沟通,弥合了设计和开发团队之间的差距。

Thymeleaf是一个类似于JSP的模板引擎,但他的区别在于,在运行项目之前,Thymeleaf也是纯HTML,可以在没有服务端的情况下进行运行,但JSP需要在服务端的支持下进行一定的转换。Thymeleaf主要有以下三个优点:

1)、Thymeleaf不管在有网络或者无网络的情况下都可以运行,因为它支持html原型,通过在html标签中增加额外的属性来达到模板+数据的展示方式,当有数据返回到页面时,Thymeleaf标签会动态地替换掉静态内容,使页面动态显示。

2)、Thymeleaf 开箱即用的特性。它提供标准和spring标准两种方言,可以直接套用模板实现JSTLOGNL表达式效果,避免每天套模板、该jstl、改标签的困扰。同时开发人员也可以扩展和创建自定义的方言。

3)、Thymeleaf 提供spring标准方言和一个与 SpringMVC 完美集成的可选模块,可以快速的实现表单绑定、属性编辑器、国际化等功能.

2、集成Redis

redis的集成,其中使用jedisPool获取jedis,并使用jedis的方法封装在RedisService当中,方便后来使用redis进行缓存

3JMeter的使用

Apache JMeterApache组织开发的基于Java的压力测试工具。用于对软件做压力测试,JMeter是一个下载即用的软件,我们使用它来进行高并发的模拟,而它会在进行模拟后给予我们一些可视化的结果,是一个很强大的工具。似乎在5.的版本中,已经不需要去配置JMETER_HOME就可以运行了。在使用过程中,最大的感想就是这个软件巨坑,不知道是不是硬件的问题,在进行500010的并发压测中,出现大量的error,而每次出现error的情况都不尽相同,让人很是头疼。

二、功能实现

功能实现这一块算是比较常规的实现,毕竟这个项目的重心在于秒杀的优化而不在于具体的实现,只是实现了常规的登录,商品列表,商品秒杀等功能。

1、全局异常控制器

为了使得代码更清爽而不十分复杂,我们实现了一个全局异常控制器。在这个类中,@ControllerAdvice是一个增强的Controller注解,使用这个Controller,可以实现以下功能:全局异常处理、全局数据绑定、全局数据预处理(SpringMVC提供,在SpringBoot中可以直接使用)@ExceptionHandler:如果只使用此注解,只能在当前Controller中处理异常,当配合ControllerAdvice一起使用的时候,就可以摆脱这种限制了。

在登陆方法中,使用@Valid注解配合全局处理器达到对手机号和密码进行格式校验的目的

2、拦截器的使用

使用拦截器与实现了HandlerMethodArgumentResolver接口的类从session中获取user对象,并作为参数注入Controller中。

3、分布式Session的使用

此处分布式session的实现主要是在登录的过程中,为用户随机生成一个token,并保存到数据库中,随后封装成cookie返回前端,并且,获取用户也由这个cookie中的token来实现。关于分布式Session一般有4种解决方案,详细可参考这篇文章:分布式Session4种解决方案。项目中所使用的就是其中的利用cookie记录Session

三、页面缓存优化

页面缓存你优化主要使用redis完成了页面缓存、URL缓存和对象缓存,并且使得频繁访问的商品详情页面和订单详情页面静态化,成为纯html页面,使用ajax异步的方式与后端交互,实现前后端分离。另外还有静态资源优化和CDN优化没有实现。

1、页面缓存

这里是一个商品列表的展示,可以看到前后代码的区别,刚开始只是直接去数据库中获取商品列表并返回到前台的页面,前台页面获取其中的信息进行填充。而下面的代码则是对页面做了一个缓存,流程大概是这样的:从redis中取缓存->从数据库中取数据->手动渲染模板->返回html

首先从redis中取相应的缓存,如果有直接返回,没有则到数据库中取商品列表的数据,并且进行手动渲染,渲染后将html页面存进redis中,并且返回。在redis的保存方法中设置了过期时间,默认是60秒。

2、对象缓存

根据token对秒杀对象进行缓存,如果缓存中还有秒杀对象的信息,则不去数据库进行取出。

3、页面静态化

页面静态化常用的技术为AngularJSVue.js等等,此处使用js简单模拟该过程,实现前后端分离。页面静态化,其实就是把用户经常访问的页面做成一个静态的页面,避免每次都要从数据库取出数据并且渲染到对应的模板文件中,从而减少服务器的压力。

页面静态化是将动态渲染生成的画面保存为html文件,放到静态文件服务器中。用户访问的是处理好的静态文件,而需要展示不同数据内容时,则可以在请求完静态化的页面后,在页面中向后端发送请求,获取属于用户的特殊数据。

页面静态化是一个以空间换时间的操作,虽然页面增多时会占用更多的空间,但提高了网站的访问速度,提升了用户的体验,还是非常值得的。

关于原来的页面,正常的前端访问后端接口,获取html页面,展示,而页面静态化,是利用ajax异步传递数据,在前台进行数据的填充,实现了前后端分离。

四、接口优化

接下来是对于秒杀接口的优化,这是最初始的秒杀接口:

刚开始的秒杀接口的业务流程是这样的:判断用户状态->获取商品库存并判断->获取秒杀订单并判断->减库存。这个过程需要调用数据库共3次。

秒杀需要面对一个很严峻的问题:如何防止超卖或者少卖?此项目中总共用了两种方式,第一种是在减库存的sql语句中加上对库存的判断,来防止库存变成负数;第二种是对数据库中的秒杀订单表中的用户id列和商品id列加上唯一索引,防止用户重复购买。

优化:秒杀静态化,通过ajax异步请求调用秒杀接口并进行轮询查看是否成功,redis预减库存减少对数据库的访问,内存标记减少redis访问,RabbitMQ队列缓冲,异步下单,增强用户体验,同时减少服务器压力。首先是使用redis预减库存,我们使用一个HashMap来存储某商品是否售空,这里用到一个方法afterPropertiesSet(),该方法会在Bean的所有属性初始化完成后调用,在这个方法中,我们从数据库中查出商品列表,将每个商品的库存存入redis中,并将库存不为0的商品在HashMap中设置为false,意为未售空。(此处应加上对商品库存是否大于0的判断)

在秒杀方法中,我们首先获取mao中的布尔值来判断是否售空决定是否进行下一步操作。使用redisdecr()来预减库存,减完判断是否小于0,如果小于0就设置map中的值为true,并且返回,否则进行下一步操作。

关于RabbitMQ的操作,我个人感觉还是有点迷的,毕竟真正的秒杀不会让你一直等待吧?(没事,咱也没秒杀到过东西,问题应该不是很大,也可能等待的时间很短人们感受不出来?这里请懂的朋友评论区指导指导呗)

此项目中使用RabbitMQ将秒杀请求入队,前端进行轮询确认是否下单成功来实现异步下单的操作。关于RabbitMQRabbitMQ是一个开源的消息代理和队列服务器,用来通过普通协议在完全不同的应用之间共享数据,或者简单的将作业排队以便让分布式服务器进行处理。

RabbitMQ有四种交换机模式,分别是Direct模式、Topic模式、Fanout模式和Headers模式。Direct模式需要将一个队列绑定到交换机上,要求该信息与一个特定的路由键完全匹配;Topic模式是将路由键和某模式进行匹配,队列需要绑定到一个模式上;Fanout模式则是不处理路由键,只需要将队列绑定到交换机上,一个发送到该交换机的信息都会被转发到与该交换机绑定的所有队列上,类似于子网传播;Headers则是以key-value形式来匹配。

队列和交换机绑定使用的是BindingBuilder.bind(队列).to(交换机)

注意此时秒杀操作并不在秒杀功能中处理,而是在RabbitMQ中的receive()方法中处理(见第三段代码)

五、安全优化

1、秒杀接口地址隐藏

在秒杀真正开始之前,我们的秒杀地址不应该是可以访问的,这样会造成极大的不公平,如果你获得秒杀地址就可以在秒杀开始之前买到商品。因此我们可以在地址中间加上一个随机的path,可以利用随机生成随机码,并把用户id和商品id作为key,把path存入redis中,在真正秒杀之前会再度把传入的pathredis中的path作比较。

2、数学公式验证码

为了减少真正进入秒杀接口的用户,在商品详情页面进行一个验证码的输入,对算式进行计算并把值存进redis,在秒杀方面之前对用户 输入的验证码和redis中的值进行比较,从而判断是否进入秒杀。

3、接口防刷

接口防刷是防止用户短时间内多次访问同一页面,可以避免某些脚本的攻击。原理是使用redis缓存,假如在k秒内访问超过5次就拦截,key值是固定前缀加上用户id,有效时间是k秒,而value则是从1开始,在不超过5次的前提下,调用redisincr方法。如果超过限制,就拒绝访问,在拦截器的preHandle()方法中返回false,拒绝访问。

但是可能会有很多的接口需要限制,所以会出现冗余代码很多的情况,并且这样也会使得接口方法变得十分复杂,代码可读性也会降低。因此我们可以声明一个相关的注解,如下:

4、用户存储

在多个方法中都需要使用秒杀用户作为参数,判断用户是否为空而决定需不需要跳转到登录页面。因此每次都要从数据库中查找相应的对象来返回。我们可以使用一个ThreadLocal来对User对象进行保存,而ThreadLocal的有效期就是在当前线程内,所以在用户访问的过程中都可以从本地拿到一个秒杀用户对象而不用访问数据库。

上一篇文章:煤矿井下电网漏电保护 下一篇文章:没有了
相关链接
发表评论
用户评论

科达产品

推荐产品

版权所有 山西科达自控股份有限公司 晋ICP备09004627号         
官方微信
新浪官方微博
腾讯官方微博