记一次高并发情况,服务器和代码修改过程记录。

            2016年12月,苹果新通知必须HTTPS。 原本我们的项目运行在windows server 2008R2(内存32G cpu 16 带宽 10M)下面,是没有任何问题的,已经正常运行半年有余了。我们服务器有2个项目 暂时叫做项目A  B。升级HTTPS  2个项目都需要 IIS7.5 是无法做到的 ,必须支持SNI 才可以。然后着手升级阿里云服务器,升级到最新版本windows server 2012R2 IIS8.5,所有配置保持不变,硬盘有SSD 换成了高效云。然后问题开始出现了。

            平常运行是没有问题的,我们每个周三都有一个秒杀活动。每个周三下午2点30分钟准时 400或者200个物品一元抢购,然后周三开始瘫痪了。 这个问题是有历史渊源的。刚入到这个公司接受项目 这个项目 是Nhibernate+webform+webservice 做的,是一个比较老的项目,中间还穿插了几个维护人自己的技术。网站没什么流量 也就运行着,然后公司开始上线秒杀活动,根本无法运行,日志测试下 发现Nhibernate 根本数据处理太慢 ,高并发 下 CPU直接爆了,这个CPU 都爆了 肯定代码问题了。然后着手换订单流程 和主页面的代码,换框架是来不及了 然后 就用ado.net 重写了,顺便加入了缓存机制。

          说下这里的缓存机制吧,但是想的是reids 来做的,也比较潮流 口碑比较好。但是当时项目着急上线 来不及给你实验了 就用了 最简单的硬盘txt 来缓存,我们网站的在线人数 是足够的。

         问题解决,秒杀活动也就正常了。很轻松 换下数据获取方式 加上缓存。

         服务器升级后问题又来了,老毛病又犯了。但是这次不一样 CPU 内存 带宽 一切正常,但是网站卡死,但是同服务器的网站B 正常运行。然后自己想想 应该不是代码的问题,毕竟成功运行了这么久了,服务器应该也没有问题 网站B 都可以正常运行,应该是程序池的原因。 开始排查服务器 研究IIS8.5,把IIS8.5的发布视频都给看了,然后在事件查看器→windows 日志→应用程序 里面 查看到错误一句英文:The state server has closed an expired TCP/IP connection. The IP address of the client is 127.0.0.1. The expired Read operation began at 01/04/2017 16:36:57。网上查下状态服务连接关闭了,转到服务看了下 asp.net state service 正常运行啊。思前顾后 也就秒杀这个时间段出现了这一个错误,然后把 状态服务 直接存到了 数据库,具体方法可以百度。以为问题解决了 真是so young 啊,没有压力测试下 就上线了,第二周又挂了 这次没有报那句英文错误了,但是还是卡死。头大了,临近年关你给我整这出,还要不要让我过年了啊。这次直接开始压力测试,但是网上的压力测试大多数都是压整个服务器起的 和我们出现的情况不太相同啊,中间找了很多的资料,看的感觉最像的就是博客园的黑色30秒事件 具体可以看连接http://www.cnblogs.com/cmt/p/3682642.html,博客园写了好几篇文章 大家可以看看,说的非常细致。也根据他们提供的解决方案 

<processModel enable="true" requestQueueLimit="5000" maxWorkerThreads="100" maxIoThreads="100" minWorkerThreads="50" minIoThreads="50"/>

对machine.config(位于C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Config)经行了修改。 然后进行压力测试(我们用的压力测试工具是WebPerformanceTest)发现某些页面 某些接口 是没有问题,有些接口有些页面 进行压力测试项目就开始卡了(我们 压力测试 进行是 本机 和一台测试服务器同时进行 并发用户数 每台是1000,一台进行10分钟压力测试
另一台是10000个请求),然后查看 性能测试页面 发现requestd current 爆了,requestd queued 没有了,在没有进行machine.cofig修改时候 requsted queued 会很大 但是不至于爆了。说明这个设置有效果,但是还是卡 还是没有根本的解决问题啊。后来把state service 又还原到以前的内存读取。

         再次分析阿里云,发现在压力测试的时候 阿里云的IO 网络 带宽会到峰值 也就达到了顶值,会不会是因为 缓存写在硬盘导致的高效云太差劲 没有SSD好。开始倔起来了啊,直接打电话找阿里云 问问题 人家小姑娘那里懂这些问题啊, 提交工单。。。。尼玛 提交工单,问题描述下提交过去。阿里云那边的答复是:二者相同,在XXX功能上面 高效云比SSD的好,顺便帮我们查看了说 带宽不够。 这个怎么可能啊 10M 带宽 这才多少人啊。 (后来我们通过IIS 日志分析请求 用的是秋式网站日志分析器 当天的请求 才 60W个,秒杀活动 当时的请求也就 2.30 到3.00的请求 才 25W 多点),靠阿里云是不行了,自己动脑丰衣足食。影响带宽,影响IO 会有那些了。图片下载,硬盘写缓存。 一个一个来 我们对图片的读取经行了一次压力测试 发现没有问题啊。这个可以排除了,剩下就是 缓存写入硬盘了。

       换缓存的读写方式,弃掉硬盘读取 缓存redis,具体怎么读写 就是网上最简单的,我们也就是对几个频繁页面进行缓存,没什么技术可言。压力测试 还是挂,但是 又换了展现方式了,我缓存是 接口读写的json ,压力测试PC 网站。我没有把所有的都缓存。诶 继续想。 

          又把问题转移到代码上面 在次进行日志测试,发现某些压力测试卡的接口 和页面 居然的底层数据库还有些 Nhibernate 获取数据,Nhibernate +sql 的获取方式,真是高估了Nhbienate了,继续换成 ADO.NET。 然后在网上又找到了一篇文章关于设置IIS 支持高并发的文章,我写在上一篇的文章中,具体可以自己查阅。然后压力测试 OK了。每个接口都压了下 发现都可以了。 具体解决方案 我也不太清楚了 毕竟改了太多设置,代码也换了不少东西。下面我会总结 我修改 的地方。前面大多数都是自己的抱怨,可以直接转移到 终极解决方案。

      中途一个小插曲,没想到 这么点用户的项目 都给我整上 伪分布式。我将 IOS pc  android 进行了分开部署,这样 就算卡了 也就卡了单方面 ,其他端还是可以参与活动,这个当作备胎方案实施吧。不怕一万就怕万一啊,领导再次说抢不了  今年就别想提加工资了。 这个也是大项目的一个 基本部署, 接口 登录 支付 PC端 静态文件 数据库 分开部署。

      终极解决方案:1.修改machine.config

                         2.根据我上一篇随笔 修改了IIS 配置 和电脑TCP/TP的连接数

                         3.硬盘缓存换成redis缓存

                         4.将项目中残余的Nbibienate+sql 全部换成 ado.net

                         5. IOS PC  Android分开部署

      具体哪一步解决了我的问题 我也不太清楚, 

                            

转载于:https://www.cnblogs.com/kainjie/p/6423467.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 首先,你需要在 Java 程序中导入 Jedis 库,并连接到 Redis 服务器。 import redis.clients.jedis.Jedis; Jedis jedis = new Jedis("localhost"); // 连接 Redis 服务器 然后,你可以使用 Redis 的 setnx 命令来实现锁定用户的功能,setnx 命令会在键不存在的情况下将键设置为一个值,如果键已经存在,则不会进行任何操作。 // 用户的 ID String userId = "12345"; // 今天的日期,格式为 yyyyMMdd String today = "20220101"; // 在用户 ID 和今天的日期的组合上设置一个锁,如果设置成功,则返回 1;如果键已经存在,则返回 0 long lockResult = jedis.setnx(userId + ":" + today, "1"); if (lockResult == 1) { // 设置锁成功,说明当前用户没有完成过今天的签到任务 // 进行签到操作 // ...... // 签到完成后,释放锁 jedis.del(userId + ":" + today); } else { // 设置锁失败,说明当前用户已经完成过今天的签到任务,无需再次完成 } 最后,你还可以使用 Redis 的 expire 命令来为锁设置过期时间,以防止程序异常中断导致锁无法释放。 // 设置锁的过期时间为 1 天 jedis.expire(userId + ":" + today, 86400); ### 回答2: 使用Java中的Redis可以很好地设计一个监控高并发情况下用户完成当天签到任务后再次重复完成签到的代码。下面是一个简单的代码示例: 首先,我们需要使用Java的Redis客户端连接到Redis服务器。可以使用Jedis或Lettuce等第三方库来访问Redis。 ```java import redis.clients.jedis.Jedis; public class RedisSigninMonitor { private Jedis jedis; private static final String PREFIX_KEY = "signin"; public RedisSigninMonitor() { jedis = new Jedis("localhost", 6379); } public boolean checkAndSetSignin(String userId) { String key = PREFIX_KEY + ":" + userId; long currentTime = System.currentTimeMillis(); String lastSigninTimeStr = jedis.get(key); long lastSigninTime = 0; if (lastSigninTimeStr != null) { lastSigninTime = Long.parseLong(lastSigninTimeStr); } // 如果上次签到时间在当天 if (isSameDay(lastSigninTime, currentTime)) { System.out.println("用户" + userId + "重复签到!"); return false; } else { jedis.set(key, String.valueOf(currentTime)); System.out.println("用户" + userId + "签到成功!"); return true; } } private boolean isSameDay(long time1, long time2) { // 将时间戳转换为当天的0点 long zeroTime1 = time1 - (time1 + 8 * 60 * 60 * 1000) % (24 * 60 * 60 * 1000); long zeroTime2 = time2 - (time2 + 8 * 60 * 60 * 1000) % (24 * 60 * 60 * 1000); return zeroTime1 == zeroTime2; } } ``` 在上述代码中,我们使用了一个`RedisSigninMonitor`类来封装了签到监控的相关逻辑。其中`checkAndSetSignin`方法用于检查用户是否重复签到并设置签到时间。在这里,我们使用了`用户ID`作为Redis中键的一部分,来唯一标识每个用户。`PREFIX_KEY`为键的前缀,可根据实际需求进行修改。 在方法实现中,我们首先通过Redis的`get`方法获取用户的上次签到时间。如果上次签到时间与当前时间在同一天,即表示用户重复签到;否则,将当前时间作为用户的新签到时间,并使用`set`方法设置到Redis中。 在实际使用中,我们可以通过调用`checkAndSetSignin`方法来判断用户是否成功签到,并进行相应操作。 ### 回答3: 首先,为了使用Redis,我们需要首先引入Redis的Java客户端。常用的有Jedis和Lettuce两个选项,这里以Jedis为例进行示范。 ```java import redis.clients.jedis.Jedis; public class SignTask { private static Jedis jedis = new Jedis("localhost"); // 连接本地Redis服务,根据实际情况更新地址和端口 // 完成签到任务 public static void completeSignTask(String userId) { // 检查用户是否已完成当天签到任务 if (!jedis.getbit("sign:task:" + userId, getCurrentDay())) { // 如果未完成,则设置用户的标志位,并进行签到操作 jedis.setbit("sign:task:" + userId, getCurrentDay(), true); jedis.incr("sign:count:" + getCurrentDay()); // 当天签到总人数自增 System.out.println("用户 " + userId + " 签到成功!"); } else { System.out.println("用户 " + userId + " 重复签到!"); } } // 获取今天的日期,作为Redis位图的索引值 private static String getCurrentDay() { return String.valueOf(System.currentTimeMillis() / (24 * 60 * 60 * 1000)); } public static void main(String[] args) { completeSignTask("user1"); completeSignTask("user1"); // 重复调用完成签到任务 } } ``` 在上述代码中,我们将用户的签到任务使用Redis的位图数据结构进行存储。通过将用户的签到情况表示为一个位图,每一位代表一天,我们可以通过GETBIT来检查用户是否已完成当天的签到任务,通过SETBIT设置用户的签到标志位。 当用户完成签到任务后,我们通过GETBIT检查用户当天的签到标志位是否为true,如果为true,则表明用户重复完成签到任务。否则,我们设置用户的签到标志位为true,并且进行相应的数据统计。 请注意,在上述示例中我们只存储了一天的签到数据。如果需要存储多天的签到数据,可以根据实际需求修改设置用户签到标志位的逻辑,比如可以记录最近7天的签到情况。 此外,我们还可以根据实际需求,在获取当天签到总人数时,使用Redis的INCR命令进行自增操作,实现高并发下的并发计数。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值