秒杀架构实践

转载自 秒杀架构实践

前言

本次采用循序渐进的方式逐步提高性能达到并发秒杀的效果,文章较长请准备好瓜子板凳(liushuizhang)。

本文所有涉及的代码:

  • https://github.com/crossoverJie/SSM

  • https://github.com/crossoverJie/distributed-redis-tool

最终架构图

先简单根据这个图谈下请求的流转,因为后面不管怎么改进这个都是没有变的。

  • 前端请求进入 web 层,对应的代码就是 controller

  • 之后将真正的库存校验、下单等请求发往 Service 层(其中 RPC 调用依然采用的 dubbo,只是更新为最新版本,本次不会过多讨论 dubbo 相关的细节,有兴趣的可以查看 基于dubbo 的分布式架构)。

  • Service 层再对数据进行落地,下单完成。

无限制

其实抛开秒杀这个场景来说正常的一个下单流程可以简单分为以下几步:

  • 校验库存

  • 扣库存

  • 创建订单

  • 支付

基于上文的架构所以我们有了以下实现:

先看看实际项目的结构:

还是和以前一样:

  • 提供出一个 API 用于 Service 层实现,以及 web 层消费。

  • web 层简单来说就是一个 SpringMVC

  • Service 层则是真正的数据落地。

  • SSM-SECONDS-KILL-ORDER-CONSUMER 则是后文会提到的 Kafka 消费。

数据库也是只有简单的两张表模拟下单:

 
  1. CREATE TABLE `stock` (

  2.  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,

  3.  `name` varchar(50) NOT NULL DEFAULT '' COMMENT '名称',

  4.  `count` int(11) NOT NULL COMMENT '库存',

  5.  `sale` int(11) NOT NULL COMMENT '已售',

  6.  `version` int(11) NOT NULL COMMENT '乐观锁,版本号',

  7.  PRIMARY KEY (`id`)

  8. ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

  9.  

  10.  

  11. CREATE TABLE `stock_order` (

  12.  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,

  13.  `sid` int(11) NOT NULL COMMENT '库存ID',

  14.  `name` varchar(30) NOT NULL DEFAULT '' COMMENT '商品名称',

  15.  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '创建时间',

  16.  PRIMARY KEY (`id`)

  17. ) ENGINE=InnoDB AUTO_INCREMENT=55 DEFAULT CHARSET=utf8;

web 层 controller 实现:

 
  1.    @Autowired

  2.    private StockService stockService;

  3.  

  4.    @Autowired

  5.    private OrderService orderService;

  6.  

  7.    @RequestMapping("/createWrongOrder/{sid}")

  8.    @ResponseBody

  9.    public String createWrongOrder(@PathVariable int sid) {

  10.        logger.info("sid=[{}]", sid);

  11.        int id = 0;

  12.        try {

  13.            id = orderService.createWrongOrder(sid);

  14.        } catch (Exception e) {

  15.            logger.error("Exception",e);

  16.        }

  17.        return String.valueOf(id);

  18.    }

其中 web 作为一个消费者调用看 OrderService 提供出来的 dubbo 服务。

Service 层, OrderService 实现:

首先是对 API 的实现(会在 API 提供出接口):

 
  1. @Service

  2. public class OrderServiceImpl implements OrderService {

  3.  

  4.    @Resource(name = "DBOrderService")

  5.    private com.crossoverJie.seconds.kill.service.OrderService orderService ;

  6.  

  7.    @Override

  8.    public int createWrongOrder(int sid) throws Exception {

  9.        return orderService.createWrongOrder(sid);

  10.    }

  11. }

这里只是简单调用了 DBOrderService 中的实现,DBOrderService 才是真正的数据落地,也就是写数据库了。

DBOrderService 实现:

 
  1. Transactional(rollbackFor = Exception.class)

  2. @Service(value = "DBOrderService")

  3. public class OrderServiceImpl implements OrderService {

  4.    @Resource(name = "DBStockService")

  5.    private com.crossoverJie.seconds.kill.service.StockService stockService;

  6.  

  7.    @Autowired

  8.    private StockOrderMapper orderMapper;

  9.  

  10.    @Override

  11.    public int createWrongOrder(int sid) throws Exception{

  12.  

  13.        //校验库存

  14.        Stock stock = checkStock(sid);

  15.  

  16.        //扣库存

  17.        saleStock(stock);

  18.  

  19.        //创建订单

  20.        int id = createOrder(stock);

  21.  

  22.        return id;

  23.    }

  24.  

  25.    private Stock checkStock(int sid) {

  26.        Stock stock = stockService.getStockById(sid);

  27.        if (stock.getSale().equals(stock.getCount())) {

  28.            throw new RuntimeException("库存不足");

  29.        }

  30.        return stock;

  31.    }

  32.  

  33.    private int saleStock(Stock stock) {

  34.        stock.setSale(stock.getSale() + 1);

  35.        return stockService.updateStockById(stock);

  36.    }

  37.  

  38.    private int createOrder(Stock stock) {

  39.        StockOrder order = new StockOrder();

  40.        order.setSid(stock.getId());

  41.        order.setName(stock.getName());

  42.        int id = orderMapper.insertSelective(order);

  43.        return id;

  44.    }        

  45.  

  46. }

预先初始化了 10 条库存。

手动调用下 createWrongOrder/1 接口发现:

库存表:

订单表:

一切看起来都没有问题,数据也正常。

但是当用 JMeter 并发测试时:

请求都响应成功,库存确实也扣完了,但是订单却生成了 124 条记录。

这显然是典型的超卖现象。

其实现在再去手动调用接口会返回库存不足,但为时晚矣。

乐观锁更新

怎么来避免上述的现象呢?

最简单的做法自然是乐观锁了,这里不过多讨论这个,不熟悉的朋友可以看下这篇。

来看看具体实现:

其实其他的都没怎么改,主要是 Service 层。

 
  1.    @Override

  2.    public int createOptimisticOrder(int sid) throws Exception {

  3.  

  4.        //校验库存

  5.        Stock stock = checkStock(sid);

  6.  

  7.        //乐观锁更新库存

  8.        saleStockOptimistic(stock);

  9.  

  10.        //创建订单

  11.        int id = createOrder(stock);

  12.  

  13.        return id;

  14.    }

  15.  

  16.    private void saleStockOptimistic(Stock stock) {

  17.        int count = stockService.updateStockByOptimistic(stock);

  18.        if (count == 0){

  19.            throw new RuntimeException("并发更新库存失败") ;

  20.        }

  21.    }

对应的 XML:

  1.    <update id="updateByOptimistic" parameterType="com.crossoverJie.seconds.kill.pojo.Stock">

  2.        update stock

  3.        <set>

  4.            sale = sale + 1,

  5.            version = version + 1,

  6.        </set>

  7.  

  8.        WHERE id = #{id,jdbcType=INTEGER}

  9.        AND version = #{version,jdbcType=INTEGER}

  10.  

  11.    </update>

同样的测试条件,我们再进行上面的测试 /createOptimisticOrder/1

这次发现无论是库存订单都是 OK 的。

查看日志发现:

很多并发请求会响应错误,这就达到了效果。

提高吞吐量

为了进一步提高秒杀时的吞吐量以及响应效率,这里的 web 和 Service 都进行了横向扩展。

  • web 利用 Nginx 进行负载。

  • Service 也是多台应用。

再用 JMeter 测试时可以直观的看到效果。

由于我是在阿里云的一台小水管服务器进行测试的,加上配置不高、应用都在同一台,所以并没有完全体现出性能上的优势( Nginx 做负载转发时候也会增加额外的网络消耗)。

shell 脚本实现简单的 CI

由于应用多台部署之后,手动发版测试的痛苦相信经历过的都有体会。

这次并没有精力去搭建完整的 CI CD,只是写了一个简单的脚本实现了自动化部署,希望对这方面没有经验的同学带来一点启发:

构建 web

 
  1. #!/bin/bash

  2.  

  3. # 构建 web 消费者

  4.  

  5. #read appname

  6.  

  7. appname="consumer"

  8. echo "input="$appname

  9.  

  10. PID=$(ps -ef | grep $appname | grep -v grep | awk '{print $2}')

  11.  

  12. # 遍历杀掉 pid

  13. for var in ${PID[@]};

  14. do

  15.    echo "loop pid= $var"

  16.    kill -9 $var

  17. done

  18.  

  19. echo "kill $appname success"

  20.  

  21. cd ..

  22.  

  23. git pull

  24.  

  25. cd SSM-SECONDS-KILL

  26.  

  27. mvn -Dmaven.test.skip=true clean package

  28.  

  29. echo "build war success"

  30.  

  31. cp /home/crossoverJie/SSM/SSM-SECONDS-KILL/SSM-SECONDS-KILL-WEB/target/SSM-SECONDS-KILL-WEB-2.2.0-SNAPSHOT.war /home/crossoverJie/tomcat/tomcat-dubbo-consumer-8083/webapps

  32. echo "cp tomcat-dubbo-consumer-8083/webapps ok!"

  33.  

  34. cp /home/crossoverJie/SSM/SSM-SECONDS-KILL/SSM-SECONDS-KILL-WEB/target/SSM-SECONDS-KILL-WEB-2.2.0-SNAPSHOT.war /home/crossoverJie/tomcat/tomcat-dubbo-consumer-7083-slave/webapps

  35. echo "cp tomcat-dubbo-consumer-7083-slave/webapps ok!"

  36.  

  37. sh /home/crossoverJie/tomcat/tomcat-dubbo-consumer-8083/bin/startup.sh

  38. echo "tomcat-dubbo-consumer-8083/bin/startup.sh success"

  39.  

  40. sh /home/crossoverJie/tomcat/tomcat-dubbo-consumer-7083-slave/bin/startup.sh

  41. echo "tomcat-dubbo-consumer-7083-slave/bin/startup.sh success"

  42.  

  43. echo "start $appname success"

构建 Service

 
  1. # 构建服务提供者

  2.  

  3. #read appname

  4.  

  5. appname="provider"

  6.  

  7. echo "input="$appname

  8.  

  9.  

  10. PID=$(ps -ef | grep $appname | grep -v grep | awk '{print $2}')

  11.  

  12. #if [ $? -eq 0 ]; then

  13. #    echo "process id:$PID"

  14. #else

  15. #    echo "process $appname not exit"

  16. #    exit

  17. #fi

  18.  

  19. # 遍历杀掉 pid

  20. for var in ${PID[@]};

  21. do

  22.    echo "loop pid= $var"

  23.    kill -9 $var

  24. done

  25.  

  26. echo "kill $appname success"

  27.  

  28.  

  29. cd ..

  30.  

  31. git pull

  32.  

  33. cd SSM-SECONDS-KILL

  34.  

  35. mvn -Dmaven.test.skip=true clean package

  36.  

  37. echo "build war success"

  38.  

  39. cp /home/crossoverJie/SSM/SSM-SECONDS-KILL/SSM-SECONDS-KILL-SERVICE/target/SSM-SECONDS-KILL-SERVICE-2.2.0-SNAPSHOT.war /home/crossoverJie/tomcat/tomcat-dubbo-provider-8080/webapps

  40.  

  41. echo "cp tomcat-dubbo-provider-8080/webapps ok!"

  42.  

  43. cp /home/crossoverJie/SSM/SSM-SECONDS-KILL/SSM-SECONDS-KILL-SERVICE/target/SSM-SECONDS-KILL-SERVICE-2.2.0-SNAPSHOT.war /home/crossoverJie/tomcat/tomcat-dubbo-provider-7080-slave/webapps

  44.  

  45. echo "cp tomcat-dubbo-provider-7080-slave/webapps ok!"

  46.  

  47. sh /home/crossoverJie/tomcat/tomcat-dubbo-provider-8080/bin/startup.sh

  48. echo "tomcat-dubbo-provider-8080/bin/startup.sh success"

  49.  

  50. sh /home/crossoverJie/tomcat/tomcat-dubbo-provider-7080-slave/bin/startup.sh

  51. echo "tomcat-dubbo-provider-8080/bin/startup.sh success"

  52.  

  53. echo "start $appname success"

之后每当我有更新,只需要执行这两个脚本就可以帮我自动构建。

都是最基础的 Linux 命令,相信大家都看得明白。

乐观锁更新 + 分布式限流

上文的结果看似没有问题,其实还差得远呢。

这里只是模拟了 300 个并发没有问题,但是当请求达到了 3000 ,3W,300W 呢?

虽说可以横向扩展可以支撑更多的请求。

但是能不能利用最少的资源解决问题呢?

其实仔细分析下会发现:

假设我的商品一共只有 10 个库存,那么无论你多少人来买其实最终也最多只有 10 人可以下单成功。

所以其中会有 99% 的请求都是无效的。

大家都知道:大多数应用数据库都是压倒骆驼的最后一根稻草。

通过 Druid 的监控来看看之前请求数据库的情况:

因为 Service 是两个应用

数据库也有 20 多个连接。

怎么样来优化呢? 其实很容易想到的就是分布式限流。

我们将并发控制在一个可控的范围之内,然后快速失败这样就能最大程度的保护系统。

distributed-redis-tool ⬆️v1.0.3

为此还对 https://github.com/crossoverJie/distributed-redis-tool 进行了小小的升级。

因为加上该组件之后所有的请求都会经过 Redis,所以对 Redis 资源的使用也是要非常小心。

API 更新

修改之后的 API 如下:

 
  1. @Configuration

  2. public class RedisLimitConfig {

  3.  

  4.    private Logger logger = LoggerFactory.getLogger(RedisLimitConfig.class);

  5.  

  6.    @Value("${redis.limit}")

  7.    private int limit;

  8.  

  9.  

  10.    @Autowired

  11.    private JedisConnectionFactory jedisConnectionFactory;

  12.  

  13.    @Bean

  14.    public RedisLimit build() {

  15.        RedisLimit redisLimit = new RedisLimit.Builder(jedisConnectionFactory, RedisToolsConstant.SINGLE)

  16.                .limit(limit)

  17.                .build();

  18.  

  19.        return redisLimit;

  20.    }

  21. }

这里构建器改用了 JedisConnectionFactory,所以得配合 Spring 来一起使用。

并在初始化时显示传入 Redis 是以集群方式部署还是单机(强烈建议集群,限流之后对 Redis 还是有一定的压力)。

限流实现

既然 API 更新了,实现自然也要修改:

 
  1.    /**

  2.     * limit traffic

  3.     * @return if true

  4.     */

  5.    public boolean limit() {

  6.  

  7.        //get connection

  8.        Object connection = getConnection();

  9.  

  10.        Object result = limitRequest(connection);

  11.  

  12.        if (FAIL_CODE != (Long) result) {

  13.            return true;

  14.        } else {

  15.            return false;

  16.        }

  17.    }

  18.  

  19.    private Object limitRequest(Object connection) {

  20.        Object result = null;

  21.        String key = String.valueOf(System.currentTimeMillis() / 1000);

  22.        if (connection instanceof Jedis){

  23.            result = ((Jedis)connection).eval(script, Collections.singletonList(key), Collections.singletonList(String.valueOf(limit)));

  24.            ((Jedis) connection).close();

  25.        }else {

  26.            result = ((JedisCluster) connection).eval(script, Collections.singletonList(key), Collections.singletonList(String.valueOf(limit)));

  27.            try {

  28.                ((JedisCluster) connection).close();

  29.            } catch (IOException e) {

  30.                logger.error("IOException",e);

  31.            }

  32.        }

  33.        return result;

  34.    }

  35.  

  36.    private Object getConnection() {

  37.        Object connection ;

  38.        if (type == RedisToolsConstant.SINGLE){

  39.            RedisConnection redisConnection = jedisConnectionFactory.getConnection();

  40.            connection = redisConnection.getNativeConnection();

  41.        }else {

  42.            RedisClusterConnection clusterConnection = jedisConnectionFactory.getClusterConnection();

  43.            connection = clusterConnection.getNativeConnection() ;

  44.        }

  45.        return connection;

  46.    }

如果是原生的 Spring 应用得采用 @SpringControllerLimit(errorCode=200)注解。

实际使用如下:

web 端:

 
  1.    /**

  2.     * 乐观锁更新库存 限流

  3.     * @param sid

  4.     * @return

  5.     */

  6.    @SpringControllerLimit(errorCode = 200)

  7.    @RequestMapping("/createOptimisticLimitOrder/{sid}")

  8.    @ResponseBody

  9.    public String createOptimisticLimitOrder(@PathVariable int sid) {

  10.        logger.info("sid=[{}]", sid);

  11.        int id = 0;

  12.        try {

  13.            id = orderService.createOptimisticOrder(sid);

  14.        } catch (Exception e) {

  15.            logger.error("Exception",e);

  16.        }

  17.        return String.valueOf(id);

  18.    }

Service 端就没什么更新了,依然是采用的乐观锁更新数据库。

再压测看下效果 /createOptimisticLimitOrderByRedis/1

首先是看结果没有问题,再看数据库连接以及并发请求数都有明显的下降

乐观锁更新 + 分布式限流 + Redis 缓存

其实仔细观察 Druid 监控数据发现这个 SQL 被多次查询:

其实这是实时查询库存的 SQL,主要是为了在每次下单之前判断是否还有库存。

这也是个优化点

这种数据我们完全可以放在内存中,效率比在数据库要高很多。

由于我们的应用是分布式的,所以堆内缓存显然不合适,Redis 就非常适合。

这次主要改造的是 Service 层:

  • 每次查询库存时走 Redis。

  • 扣库存时更新 Redis。

  • 需要提前将库存信息写入 Redis(手动或者程序自动都可以)。

主要代码如下:

 
  1.    @Override

  2.    public int createOptimisticOrderUseRedis(int sid) throws Exception {

  3.        //检验库存,从 Redis 获取

  4.        Stock stock = checkStockByRedis(sid);

  5.  

  6.        //乐观锁更新库存 以及更新 Redis

  7.        saleStockOptimisticByRedis(stock);

  8.  

  9.        //创建订单

  10.        int id = createOrder(stock);

  11.        return id ;

  12.    }

  13.  

  14.  

  15.    private Stock checkStockByRedis(int sid) throws Exception {

  16.        Integer count = Integer.parseInt(redisTemplate.opsForValue().get(RedisKeysConstant.STOCK_COUNT + sid));

  17.        Integer sale = Integer.parseInt(redisTemplate.opsForValue().get(RedisKeysConstant.STOCK_SALE + sid));

  18.        if (count.equals(sale)){

  19.            throw new RuntimeException("库存不足 Redis currentCount=" + sale);

  20.        }

  21.        Integer version = Integer.parseInt(redisTemplate.opsForValue().get(RedisKeysConstant.STOCK_VERSION + sid));

  22.        Stock stock = new Stock() ;

  23.        stock.setId(sid);

  24.        stock.setCount(count);

  25.        stock.setSale(sale);

  26.        stock.setVersion(version);

  27.  

  28.        return stock;

  29.    }    

  30.  

  31.  

  32.    /**

  33.     * 乐观锁更新数据库 还要更新 Redis

  34.     * @param stock

  35.     */

  36.    private void saleStockOptimisticByRedis(Stock stock) {

  37.        int count = stockService.updateStockByOptimistic(stock);

  38.        if (count == 0){

  39.            throw new RuntimeException("并发更新库存失败") ;

  40.        }

  41.        //自增

  42.        redisTemplate.opsForValue().increment(RedisKeysConstant.STOCK_SALE + stock.getId(),1) ;

  43.        redisTemplate.opsForValue().increment(RedisKeysConstant.STOCK_VERSION + stock.getId(),1) ;

  44.    }    

压测看看实际效果 /createOptimisticLimitOrderByRedis/1

最后发现数据没问题,数据库的请求与并发也都下来了。

乐观锁更新 + 分布式限流 + Redis 缓存 + Kafka 异步

最后的优化还是想如何来再次提高吞吐量以及性能的。

我们上文所有例子其实都是同步请求,完全可以利用同步转异步来提高性能啊。

这里我们将写订单以及更新库存的操作进行异步化,利用 Kafka 来进行解耦和队列的作用。

每当一个请求通过了限流到达了 Service 层通过了库存校验之后就将订单信息发给 Kafka ,这样一个请求就可以直接返回了。

消费程序再对数据进行入库落地。

因为异步了,所以最终需要采取回调或者是其他提醒的方式提醒用户购买完成。

这里代码较多就不贴了,消费程序其实就是把之前的 Service 层的逻辑重写了一遍,不过采用的是 SpringBoot。

感兴趣的朋友可以看下。

https://github.com/crossoverJie/SSM/tree/master/SSM-SECONDS-KILL/SSM-SECONDS-KILL-ORDER-CONSUMER

总结

其实经过上面的一顿优化总结起来无非就是以下几点:

  • 尽量将请求拦截在上游。

  • 还可以根据 UID 进行限流。

  • 最大程度的减少请求落到 DB。

  • 多利用缓存。

  • 同步操作异步化。

  • fail fast,尽早失败,保护应用。

码字不易,这应该是我写过字数最多的了,想想当年高中 800 字的作文都憋不出来

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/327425.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

负载均衡Ribbon和Feign---SpringCloud

负载均衡Ribbon和Feign Ribbon负载均衡(基于客户端) 6.1 负载均衡以及Ribbon Ribbon是什么&#xff1f; Spring Cloud Ribbon 是基于Netflix Ribbon 实现的一套客户端负载均衡的工具。简单的说&#xff0c;Ribbon 是 Netflix 发布的开源项目&#xff0c;主要功能是提供客户端…

ASP.NET Core 在 Swagger UI 中显示自定义的 Header Token

Swagger 是个好东西&#xff0c;对于前后端分离的网站来说&#xff0c;不仅是提高前后端开发人员沟通效率的利器&#xff0c;也大大方便了后端人员测试 API。有时候&#xff0c;API 中可能需要在 Header 中设置认证参数&#xff0c;比如 authToken&#xff0c;这样的功能我们通…

nginx,excel模板下载

nginx&#xff0c;excel模板下载 weixin_30814223 2018-08-27 10:26:00 245 收藏 版权 spring boot项目&#xff0c;使用nginx服务器 最近在做一个功能是excel文件上传&#xff0c;并将其中的数据入库&#xff0c;同时还有一个文件模板下载 现在说一说这个文件模板下载 …

方舟非主机服务器无限距离,方舟非专业服务器距离限制怎么解除 | 手游网游页游攻略大全...

发布时间&#xff1a;2016-08-03方舟适者生存服务器进不去 进不去服务器解决办法攻略.服务器进不去怎么办?很多玩家无法进入服务器,主要是两种情况,一种是点服务器游戏就重启,一种是进不去,这里给大家介绍解决方法. 一.点服务器 ...标签&#xff1a;游戏攻略 游戏秘籍 方舟&am…

zookeeper  虚拟机zookeeper和 win10java代码连接

28_支付服务注册进zookeeper 上面的都复习了 18 zookeeper替换Eureka zookeeper已经在你的centeros7上配置成功了 前提要求 19 20 21 22 zookeeper在centeros7 java代码在win10 23 24 25 我是在虚拟机新建的 因为视频是虚拟机的 26 27 28 http://127.0.0.1:80…

Hystrix---SpringCloud

Hystrix 服务熔断 分布式系统面临的问题 复杂分布式体系结构中的应用程序有数十个依赖关系&#xff0c;每个依赖关系在某些时候将不可避免失败&#xff01; 服务雪崩 多个微服务之间调用的时候&#xff0c;假设微服务A调用微服务B和微服务C&#xff0c;微服务B和微服务C又调…

ASP.NET Core 导入导出Excel xlsx 文件

ASP.NET Core 使用EPPlus.Core导入导出Excel xlsx 文件&#xff0c;EPPlus.Core支持Excel 2007/2010 xlsx文件导入导出&#xff0c;可以运行在Windows, Linux和Mac。 EPPlus.Core 是基于EPPlus 更改而来&#xff0c;在Linux 下需要安装libgdiplus 。 EPPlus&#xff1a;http://…

Zull路由网关---SpringCloud

Zull路由网关 概述 什么是zuul? Zull包含了对请求的路由(用来跳转的)和过滤两个最主要功能&#xff1a; 其中路由功能负责将外部请求转发到具体的微服务实例上&#xff0c;是实现外部访问统一入口的基础&#xff0c;而过滤器功能则负责对请求的处理过程进行干预&#xff0c;…

微软称开源.NET吸引了更多开发者

两年前&#xff0c;微软出人意料的宣布开源 .NET 框架&#xff0c;两年后的今天微软表示开源 .NET 取得了成功。 .NET 项目吸引了更多的新开发者&#xff0c;虽然开源没有带来直接的收入&#xff0c;但却可能产生间接的收入&#xff0c;比如吸引更多人使用微软的云计算平台或开…

网站压力测试软件(Apache JMeter)2.13 官方版 Error: Unable to access jarfile ApacheJMeter.jar

https://jmeter.apache.org/download_jmeter.cgi 官网 https://archive.apache.org/dist/jmeter/binaries/ 下载地址 下载没有src的文件 使用方法 本录制&#xff0c;JMeter启用WEB代理&#xff0c;浏览器把代理上网设置为JMeter所在的IP地址&#xff0c;自己电脑就是127.…

win10系统用户访问ftp服务器被拒绝,关于windows2003下ftp用户名无法访问FTP服务器的问题...

关于windows 2003下建设FTP的步骤我不再详解&#xff0c;主要说两个我们经常出错的两个小问题&#xff0c;这两个小问题往往我们很容易忽视&#xff0c;使我们无法访问自己建立的FTP服务器。问题1&#xff1a;当我们建立一个FTP服务器选用“不隔离用户”选项并且选用不允许匿名…

SpringCloud Config 分布式配置

SpringCloud Config 分布式配置 Dalston.RELEASE Spring Cloud Config为分布式系统中的外部配置提供服务器和客户端支持。使用Config Server&#xff0c;您可以在所有环境中管理应用程序的外部属性。客户端和服务器上的概念映射与Spring Environment和PropertySource抽象相同…

.NET泛型初探

总所周知&#xff0c;.NET出现在.net framework 2.0&#xff0c;为什么要在2.0引入泛型那&#xff0c;因为微软在开始开发.net框架时并没有想过多个类型参数传输时对方法的重构&#xff0c;这样一来&#xff0c;开发人员就要面对传输多种类型的参数而不得以写多个方法&#xff…

如何将idea自带的maven添加到环境变量

如何将idea自带的maven添加到环境变量 标签&#xff1a; maven idea 2018-07-10 阅读(3081) 想要通过命令形式在cmd操作IntelliJ IDEA自带的maven&#xff0c;那么就必须配置idea的maven环境变量。 下面以window 10 和 idea 2016.1为例&#xff1a; 第一步&#xff1a;找到…

三国志战略版360区S4服务器合并信息,三国志战略版pk赛季怎么转区?s4转区规则[多图]...

三国志战略版pk赛季是全新的开始&#xff0c;那么如果有的玩家想要转区的话&#xff0c;需要有哪些方法或者说是条件呢&#xff1f;下面来了解下&#xff01;三国志战略版pk赛季怎么转区&#xff1f;一、多久转服因为转服只有赛季快结束&#xff0c;也就是赛季末期才会开启&…

SpringCloud(笔记)

简介 学习前提 熟练使用SpringBoot 微服务快速开发框架 了解过Dubbo Zookeeper 分布式基础 电脑配置内存不低于8G(我自己的是16G) 给大家看下多个服务跑起来后的内存开销图&#xff1a; 文章大纲 微服务架构面临的四个核心问题&#xff1f; 1.服务很多&#xff0c;客…

java身份证号码正则表达式校验(亲测可用) Java正则校验手机号

java身份证号码正则表达式校验&#xff08;亲测可用&#xff09; // 原文&#xff1a;https://blog.csdn.net/u011106915/article/details/76066985 public class IDUtils { public static boolean isIDNumber(String IDNumber) { if (IDNumber null || "&qu…

博客园官方 NuGet镜像上线试运行

为解决国内访问NuGet服务器速度不稳定的问题&#xff0c;我们用阿里云服务器搭建了一个NuGet镜像&#xff0c;目前已上线试运行。 使用NuGet镜像源的方法如下&#xff1a; 1&#xff09;NuGet镜像源地址&#xff1a;https://nuget.cnblogs.com/v3/index.json 2&#xff09;在Nu…

Java synchronized 中的while 和 notifyAll

转载自 Java synchronized 中的while 和 notifyAll 问题1 为什么是while 而不是if 大多数人都知道常见的使用synchronized代码: synchronized (obj) {while (check pass) {wait();}// do your business } 那么问题是为啥这里是while而不是if呢? 这个问题 我最开始也想了很…

Java并发编程:Lock

转载自 Java并发编程&#xff1a;Lock 一.synchronized的缺陷 synchronized是java中的一个关键字&#xff0c;也就是说是Java语言内置的特性。那么为什么会出现Lock呢&#xff1f; 在上面一篇文章中&#xff0c;我们了解到如果一个代码块被synchronized修饰了&#xff0c;当一个…