高并发秒杀场景优化思路及基于redis的核心功能实现

背景分析

在考虑详细设计之前,我们先意淫一下秒杀场景,挖掘出场景具备的一些特性:

➟系统、数据场景特性

1、读多写少:秒杀场景中有效操作远远小于无效操作,所谓的无效操作即是访问了秒杀场景中的相关内容,却最终没有促成交易的操作。
2、顺时流量爆增导致了系统响应慢,甚至影响到其他业务功能。
3、秒杀的“量”因为抢的人多而导致超“量”被抢的情况。

➟用户行为特性

1、作为具有中国特色的中国大妈,在秒杀抢商品的捉急心态下,由于服务器压力大,响应比平时慢,她怎么能停止不断点提交的操作,直到页面有反馈呢?别说大妈了,就是知道原理的我们,有时候也管不住自己的手,不停的点点点下单,抢不抢得到是后话,先点爽了再说。
2、有票的江湖就有“黄牛党”,作为一个票务秒杀,如果缺少了“黄牛党”的存在就好像少了点什么。“黄牛党”往往使用软件、脚本进行自动抢票,软件通过封装场景相关数据包进行模拟正常用户操作行为下单。所以它不仅不依赖前端页面式的交互,同时还能在很短的时间内发起大量线程模拟下单数据包提交。

系统层面的分析

一个简单的系统包含了这些分层:1、页面层→站点层→控制层→服务层→数据库层。而复杂的系统中控制层可能还包括网关、服务中心以及复杂的服务层和其他组件等。可是无论什么结构,性能的瓶颈都在数据库层。所以减少数据库层的操作是整个场景实现的核心问题,而为了减轻服务器压力以及对其他业务的影响,我们尽量将无效流量拦截在上层,同时尽可能的减少每一层的性能消耗。

逐层分流

根据以上的分析,我们来看看每一层我们能做点什么来减轻下层的流量。

1、页面层

页面层是正常用户(非黄牛党)访问的入口,还记得在用户行为特性中出现的“点点点”场景吗?没错,这个场景会使得我们的无效流量进一步增加,特别是服务器压力越大响应越慢的时候无效流量反而增加的越多。而要解决这个问题也很容易,在页面层增加一些JS代码来控制一定时间内或者本次提交响应完成前的多次提交就可以。简单轻松有效,一小段JS代码在服务器响应慢的时候,也许就能减少几倍的无效流量。由于实现很简单,这里就不贴出代码了。

2、站点层

一个请求过来时,由站点层首先处理。分析一下请求包含的内容,不难发现除了有动态资源(需要从后台读取数据而生成的资源)也有静态资源(CSS,JS,图片固化资源等)。而如果都由一个服务器统一响应,无疑会增加该服务器的压力,特别是一个功能简单的页面静态资源占比可能不比动态资源少。所以分离静态资源会成为服务器减压的一种好方式,分离的方式常用的包含两种:
一种是常用的页面缓存。通过静态资源的本地化缓存,减少重复访问页面时静态文件的服务端获取。
而项目中如果使用了apache、nginx等,则可以用它们对静态文件做分离。
除此之外,站点层还可以玩其他花样来进一步减轻流量:
如静态资源太大时,可以使用压缩技术减少高并发下的网络IO。
如果静态资源太多时,可以使用静态资源打包方式减少页面的请求量。
对于用户基数大的系统时,还可以采取CDN流量分流等方法减轻请求的集中化和响应慢问题。
对于“黄牛党”,这里也能做到部分场景下的限流。比如通过nginx扩展自定义插件的方式,可以限制某IP或者某USER_ID访问同一网页的频率。不过这种方式仅仅能解决一些初级“黄牛党”,专业点的“黄牛党”都会有自己的IP代理池以及多个账号来绕过这样的限制。
当使用apache、nginx作为代理时,还可以结合他们提供的负载均衡功能来对内部访问负荷量进行优化。
对于大型系统来说,可能还有软件层面的网关,也可以做一些策略优化访问,起到减压服务器的作用。

3、服务层

1、核心实现:
服务层首先要保证数据的正确性,既不超库存,或者抢红包场景不能超过红包总额等。这里有两种思路的方案:
(1)并发量不大情况下的简单粗暴的数据库乐观锁:
update product set amout = amout - #amout where id = #id and amout - #amout >=0;
这样的写法是使用了数据库自身提供的乐观锁机制,在amout修改过程中不小于0由数据库保证。这个方法的优点是简单,可靠性最强(因为是数据库保障其可靠性)。缺点也很明显,并发量有限。一般来说性能好点的服务器中,如果是高速机械硬盘可以达到300左右的最大数据库并发量,固态硬盘则达到700左右。
(2)高并发分布式乐观锁机制(CAS机制check and set):
当并发量大大超过数据库极限时,就需要引用分布式缓存的方式来解决。常用的缓存有memcached和Redis等。如果仅仅从秒杀的场景来说,memcached比Redis更合适主要是因为:
A、单纯从秒杀场景memcache更合适,因为它的模型是多线程,更合适高并发写的场景。redis的模型是单线程,边读边写在并发量大的时候会相对慢一些。
B、memcache自带CAS光环,有相关函数直接调用;redis虽然也有办法实现,但是还是需要自己实现。
整体方案图:https://www.processon.com/view/5a2e0435e4b0d8b7bf78d9f2
cas原理图:

image.png

由于cas机制中可能需要多次循环来尝试把扣减后的库存回写redis中,所以在代码实现时,多增加了一次库存从redis中获取是否为0的判断。也就是说第一次的判断是否超库存可以是在另外一个redis实例中的库存数量,而第二次判断是否超库存在另外个redis实例中,并且包含在cas机制的实现中。由于他们不在一个redis中,所以需要一个线程来同步判断在参与了cas机制的redis存的库存为0时,同步到未参与cas机制的redis存的库存。从而让第一道未参与cas机制的库存量直接限流,实现热点隔离的目的。(详见后面的代码,同步线程未实现,如有需要自行实现)
2、其他优化
如果项目中已经有一些支持限流、降级的组件,比如dubbo、hystrix等,则不需要以上方法在代码中控制限流。
实际情况中如果请求量太大,而且远远大于库存的情况下,还可以处理过多的请求来减小流量,处理的方式可以包括:分批放行,定量放行,随机丢弃请求(保留的请求量需要大于库存量)等方式。
3、黄牛党的优化
对于一些绕过前端,直接通过请求来刷票的资深黄牛党,传统的方法都不能很好的防范,如果非得防范,目前比较主流的一种方法就是通过加密或者混淆的方式,让黄牛党绕过前端后无法正确访问后端资源。但是这种方式伤敌1000自伤800,代价比较大。

实现代码:
package com.my.miaosa.service.impl;

import java.util.List;
import java.util.UUID;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import com.alibaba.fastjson.JSON;
import com.my.miaosa.entity.dto.FastBuyBusinessDTO;
import com.my.miaosa.entity.dto.Order;
import com.my.miaosa.service.FastBuyService;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;

@Service
public class FastBuyServiceImpl implements FastBuyService {
    private static Logger logger = LoggerFactory.getLogger("");

    /**
     * 
     * @param jedis 第一道过滤流量分离的redis主要用于场景隔离,不影响其他业务,如不需要隔离可以和jedisMain是同一个实例
     * @param jedisMain 主要业务redis实例
     * @param fastBuyBusinessDTO 秒杀场景商品基础信息封装类
     * @param orderUserName 关联下单用户名(或ID)
     * @param orders 本商品下单数量
     * @return
     */
    public String fastBuyProductOrders(Jedis jedis,Jedis jedisMain,FastBuyBusinessDTO fastBuyBusinessDTO, String orderUserName,int orders) {
        //过滤部分恶意脚本,不从redis中获取数据直接和初始化数据判断,redis减轻压力,预防部分恶意刷单。
        //伪判断预防一些恶意脚本
        if (!this.allowProductAmout(fastBuyBusinessDTO.getMaxAmout(), orders)) {
            return "抢购商品不足,抢购失败!";
        }
        
        //判断下单是否超库存,从redis中获取当前库存
        if(!this.allowFastBuyProduc(fastBuyBusinessDTO.getProductAmoutId(), orders, jedis)) {
            return "抢购商品不足,抢购失败!";
        }
        
        return this.fastBuyProduct(jedisMain, fastBuyBusinessDTO.getProductAmoutId(), fastBuyBusinessDTO.getOrderListId(), orderUserName, orders, fastBuyBusinessDTO.getProductId());
        
        
    }
    
    /**
     * 初步判断下单量是否超出剩余量
     * return 超
     */
    public boolean allowFastBuyProduc(String productAmoutId,int orders,Jedis jedis) {
        
        //一阶段过滤流量、尽可能不写REDIS,提高性能
        //判断是否还有库存
        //预拿一次,如果剩余商品已经<=0,直接返回结果过滤流量
        int prdNum = Integer.parseInt(jedis.get(productAmoutId));
        
        //判断所下的单数量是否在剩余范围内
        return this.allowProductAmout(prdNum, orders);
    }
    
    /**
     * 
     * @param jedis
     * @param amoutId redis中的存本次活动商品总量的KEY
     * @param ordersId redis中存本次活动商品抢购成功的用户信息队列的KEY
     * @param orders 下单数量
     * @param orderUserName 购买人
     * @param productId 商品标识唯一ID
     * @return
     */
    public String fastBuyProduct(Jedis jedis, String amoutId,String ordersId, String orderUserName,int orders,String productId){
        String result = "抢商品失败咯!";
        if (logger.isInfoEnabled()) {
            logger.info(orderUserName + "开始抢票-" + orders + " 张!!");
        }
        
        //TODO 根据实际业务和测试情况,可以用for限制重试抢票次数
        while (true) {
            int i = 0; i++;
            if (logger.isInfoEnabled()) {
                logger.info(orderUserName + "---第" + i + "次抢票");
            }
            try {
                jedis.watch(amoutId,ordersId);// 监视key ,如果在以下事务执行之前key的值被其他命令所改动,那么事务将被打断
                int prdNum = Integer.parseInt(jedis.get(amoutId));
                if (this.allowProductAmout(prdNum, orders)) {
                    //TODO 1、商品规则是否满足根据实际情况加
                    //TODO 2、分析用户规则根据实际情况加
                    
                    Transaction transaction = jedis.multi();
                    transaction.set(amoutId, String.valueOf(prdNum - orders));//如果可以一次购买多张需要修改,对应下面的抢购成功的用户入队列也需要修改。
                    // 把当前抢到商品的用户记录到下单队列中,sadd不允许插入重复内容。读队列smembers,读的时候不会出队列,不会改变队列内容,所以不影响CAS中的写入。获取长度scard
                    //封装成JSON格式后提交队列
                    transaction.sadd(ordersId, this.createOrdersString(orderUserName, productId, orders));
                    List<Object> res = transaction.exec();

                    // 事务提交后如果为null,说明key值在本次事务提交前已经被改变,本次事务不执行。
                    if (res == null || res.isEmpty()) {
                        if (logger.isInfoEnabled()) {
                            logger.info( orderUserName + "---第" + i  + "次抢-----没有抢到商品,正在重试");
                        }
                    } else {
                        result = "抢购成功!";
                        if (logger.isInfoEnabled()) {
                            logger.info( orderUserName + "---第" + i  + "次抢-----抢到商品-");
                        }
                        break;
                    }
                    
                } else {
                    result = "库存为0,本商品已经被抢空了哦,欢迎下次光临88";
                    break;
                }
            } catch (Exception e) {
                logger.error("抢购出错:" + e);
            } finally {
                jedis.unwatch();
                
            }
        }
        
        return result;
    }
    
    
    /**
     * 是否在库存范围内
     * @return 范围内true、范围外false
     */
    private boolean allowProductAmout(int prdNum,int orders) {
        
        //商品已经没有库存
        if (prdNum <= 0) {return false;}
        //商品当前库存不够支付订单量
        if (prdNum - orders < 0) {return false;}
        
        return true;
    }
    
    /**
     * 转JSON格式
     * @param orderUserName
     * @param productId
     * @param orderCount
     * @return
     */
    public String[] createOrdersString (String orderUserName,String productId,int orderCount) {
        String[] result = new String[orderCount];
        for (int i = 0; i < result.length; i++) {
            result[i] = JSON.toJSONString(new Order(orderUserName,productId,UUID.randomUUID().toString()));
        }
        
        return result;
    }
}
package com.my.miaosa.entity.dto;

import java.io.Serializable;

public class FastBuyBusinessDTO implements Serializable {
    
    private String id;//

    private String productId;//商品主键

    private String productAmoutId;//商品剩余量ID   String
    
    private String orderListId;//关联的下订成功客户列表(缓存中的) 集合
    
    
    //扩展规则时候用的,暂时没用
    private int maxTransaction; //本次抢购同一用户最多可以购买几个的上限值
    
    private int maxTransactionNumber;//同一用户一次最多可以抢购几个商品
    
    private int maxRepeatBuy;//同一用户是否可以重复购买,如果否则为0.如果可以则大于0,表示可以重复参与本次抢购多少次。如单个用户可以参与本次抢购3次,则这里为3
    
    private int maxAmout;//最大购买量,既本次活动本商品总量

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getProductId() {
        return productId;
    }

    public void setProductId(String productId) {
        this.productId = productId;
    }

    public String getProductAmoutId() {
        return productAmoutId;
    }

    public void setProductAmoutId(String productAmoutId) {
        this.productAmoutId = productAmoutId;
    }

    public String getOrderListId() {
        return orderListId;
    }

    public void setOrderListId(String orderListId) {
        this.orderListId = orderListId;
    }

    public int getMaxTransaction() {
        return maxTransaction;
    }

    public void setMaxTransaction(int maxTransaction) {
        this.maxTransaction = maxTransaction;
    }

    public int getMaxTransactionNumber() {
        return maxTransactionNumber;
    }

    public void setMaxTransactionNumber(int maxTransactionNumber) {
        this.maxTransactionNumber = maxTransactionNumber;
    }

    public int getMaxRepeatBuy() {
        return maxRepeatBuy;
    }

    public void setMaxRepeatBuy(int maxRepeatBuy) {
        this.maxRepeatBuy = maxRepeatBuy;
    }

    public int getMaxAmout() {
        return maxAmout;
    }

    public void setMaxAmout(int maxAmout) {
        this.maxAmout = maxAmout;
    }
}
package com.my.miaosa.entity.dto;

import java.text.SimpleDateFormat;

/**
 * 尽量简短,减少redis读写压力
 * @ClassName Order
 * @Description 
 * @author Administrator
 * @date 2017年12月12日 下午2:05:40
 * @version
 *
 */
public class Order {

    private String oun;//orderUserName下单用户名(或ID)
    
    private String opn;//orderProductName下单产品名(或ID)

    private String time;
    
    /**
     * 注意,本ID在本次抢购中必须是唯一ID,不然可能出现最终的下单记录总数和已被下单产品数量不一致(记录的下单量小于出库商品总量)。
     */
    private String id;//本ID只有在生成订单号时起作用,主要是为了区分一次下多个单导致的数据内容一致,从而使得sadd覆盖少订单问题,不使用UUID减轻网络和缓冲IO,使用时间戳有精度不够,容易重复
    
    //必须要有空的构造函数,否则fastjson转换报错
    public Order() {
        
    }
    
    public Order(String orderUserName,String orderProductName,String currectId) {
        this.oun = orderUserName;
        this.opn = orderProductName;
        this.id = currectId;
        this.time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new java.util.Date());
    }
    
    public String getOun() {
        return oun;
    }

    public void setOun(String oun) {
        this.oun = oun;
    }

    public String getOpn() {
        return opn;
    }

    public void setOpn(String opn) {
        this.opn = opn;
    }

    public String getTime() {
        return time;
    }

    public void setTime(String time) {
        this.time = time;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }   
}

测试类:

package com.my.miaosa.test;

import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.CountDownLatch;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.test.context.ContextConfiguration;

import com.alibaba.fastjson.JSON;
import com.my.miaosa.entity.dto.FastBuyBusinessDTO;
import com.my.miaosa.entity.dto.Order;
import com.my.miaosa.service.FastBuyService;
import com.my.miaosa.service.impl.FastBuyServiceImpl;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

@ContextConfiguration("classpath:applicationContext.xml")
public class FastBuyTest {
    
    private static Jedis jedis1;
    private static Jedis jedis2;
    private static JedisPool pool;
    private static JedisPoolConfig config;
    
    //业务数据设置
    private static FastBuyBusinessDTO fastBuyBusinessDTO;
    private final static int MAX_AMOUT = 10;//本次活动总商品量
    
    //测试线程数据设置
    private static CountDownLatch latch;
    //初始化几个线程等待抢商品
    private final static int THREAD_LENG = 200;
    
    @Before
    public void init() {
        initRedisPool();//初始化redis数据
        fastBuyBusinessDTO = new FastBuyBusinessDTO();
        initFastBuy();//初始化业务数据
        
    }
    
    @After
    public void colseResources() {
        jedis1.close();
        pool.close();
    }
    
    /**
     * 模拟高并发场景(1秒内,1000的并发量)
     * @throws InterruptedException
     */
    @Test
    public void fastBuyProductTest() throws InterruptedException {
        System.out.println("开始产品量:" + jedis1.get(fastBuyBusinessDTO.getProductAmoutId()));
        System.out.println("开始订单量:" + jedis1.scard(fastBuyBusinessDTO.getOrderListId()));
        
        for(int i =0;i<THREAD_LENG + 1;i++){
            Thread.sleep(20L);
            String orderUserName = "orderUserName_" + i;//模拟高并发情况下,正常情况的多用户下单
//          String orderUserName = "orderUserName_" + "0";//模拟高并发情况下,单个用户多次下单(如黄牛党)
            int orders = 3;
            int ordersRandom =  (int)(1 + Math.random()*(4-1 + 1));
            Thread th = new Thread(new TestThread(pool,orderUserName,ordersRandom));
            th.setName("THREADDDDD_"+i);
            System.out.println(th.getName()+"inited...");
            th.start();
            latch.countDown();//业务调用完成,计数器减一
        }
        Thread.sleep(3000);
        
        //显示订单结果,并把订单结果转换成order对象
        if (true) {
            Set<String> orders = jedis1.smembers(fastBuyBusinessDTO.getOrderListId());
            
            Iterator<String> it = orders.iterator();  
            while (it.hasNext()) {
              Order order = (Order)JSON.parseObject(it.next(), Order.class);
              System.out.println("userid:" + order.getOun() + "-----productId:" + order.getOpn()  + "------orderTime:" + order.getTime() );
            }
        }
        
        System.out.println("内存剩余产品:" + jedis1.get(fastBuyBusinessDTO.getProductAmoutId()));
        System.out.println("内存订单量:" + jedis1.scard(fastBuyBusinessDTO.getOrderListId()));
    }
    
    /**
     * 初始抢购
     */
    public static void initFastBuy() {
        fastBuyBusinessDTO.setId("1");
        fastBuyBusinessDTO.setProductAmoutId("product_amout_id_1");
        fastBuyBusinessDTO.setProductId("product_id_1");
        fastBuyBusinessDTO.setOrderListId("order_list_id_1");
        fastBuyBusinessDTO.setMaxAmout(MAX_AMOUT);
        
        String key = fastBuyBusinessDTO.getProductAmoutId();
        String clientList = fastBuyBusinessDTO.getOrderListId();// 抢购到商品的顾客列表
        if (jedis1.exists(key)) {
            jedis1.del(key);
        }
        
        if (jedis1.exists(clientList)) {
            jedis1.del(clientList);
        }
        jedis1.set(key, String.valueOf(MAX_AMOUT));// 初始化
    }
    
    public static void initRedisPool() {
        jedis1 = new Jedis("127.0.0.1",6379);
        jedis2 = new Jedis("127.0.0.1",6379);
        //利用Redis连接池,保证多个线程利用多个连接,充分模拟并发性
        config = new JedisPoolConfig();
        config.setMaxIdle(10);
        config.setMaxWaitMillis(1000);
        config.setMaxTotal(THREAD_LENG + 1);
        pool = new JedisPool(config, "127.0.0.1", 6379);
        //利用ExecutorService 管理线程
//        service = Executors.newFixedThreadPool(THREAD_LENG);
        //CountDownLatch保证主线程在全部线程结束之后退出
        latch = new CountDownLatch(THREAD_LENG);
    }
    
    public static class TestThread implements Runnable {
        private Jedis cli;
        private JedisPool pool;
        private FastBuyService fs = new FastBuyServiceImpl();
        private String orderUserName;
        private int orders;
        
        public TestThread(JedisPool pool,String orderUserName,int orders) {
            cli = pool.getResource();
            this.pool = pool;
            this.orderUserName = orderUserName;
            this.orders = orders;
        }
        
        public TestThread(Jedis jedis,String orderUserName,int orders) {
            cli = jedis;
            this.orderUserName = orderUserName;
            this.orders = orders;
        }

        public void run() {
            try{
                latch.await();
                fs.fastBuyProductOrders(cli, cli, fastBuyBusinessDTO,orderUserName , orders);
            }catch(Exception e){
                pool.close();
            }
        }
    }
}

POM.XML

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.my</groupId>
    <artifactId>miaosa</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <dependencies>

        <!-- spring核心依赖 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>4.3.3.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>4.3.3.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
            <version>4.3.3.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>4.3.3.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>4.3.3.RELEASE</version>
        </dependency>

        <!-- config jedis data and client jar -->
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-redis</artifactId>
            <version>1.7.1.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>2.7.2</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.21</version>
        </dependency>

        <!-- 单元测试相关依赖 -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>4.3.2.RELEASE</version>
            <scope>test</scope>
        </dependency>

        <!-- 日志相关依赖 -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.10</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.1.2</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
            <version>1.1.2</version>
        </dependency>

    </dependencies>


</project>

applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:c="http://www.springframework.org/schema/c"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:cache="http://www.springframework.org/schema/cache" xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:redisson="http://redisson.org/schema/redisson"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
                        http://www.springframework.org/schema/beans/spring-beans.xsd
                        http://www.springframework.org/schema/context 
                        http://www.springframework.org/schema/context/spring-context-4.3.xsd
                        http://www.springframework.org/schema/cache
                        http://www.springframework.org/schema/cache/spring-cache.xsd
                        http://www.springframework.org/schema/tx 
                        http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
                        http://redisson.org/schema/redisson
                        http://redisson.org/schema/redisson/redisson.xsd">

                        

    <context:property-placeholder location="classpath:db.properties"
        ignore-unresolvable="true" />

    
    <context:component-scan base-package="com.*">
    </context:component-scan>
    
    <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
        <property name="maxTotal" value="100" />
        <property name="maxIdle" value="10" />
    </bean>

    <bean id="jedisConnectionFactory"
        class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
        destroy-method="destroy">
        <property name="hostName" value="localhost" />
        <property name="port" value="6379" />
        <property name="database" value="2" />
        <property name="timeout" value="3000" />
        <property name="usePool" value="true" />
        <property name="poolConfig" ref="jedisPoolConfig" />
    </bean>

    <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
        <property name="connectionFactory" ref="jedisConnectionFactory" />
        <property name="keySerializer">
            <bean
                class="org.springframework.data.redis.serializer.StringRedisSerializer" />
        </property>
        <property name="valueSerializer">
            <bean
                class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer" />
        </property>
        
        <property name="enableTransactionSupport" value="false"></property>
    </bean>
    
</beans>
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,287评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,346评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,277评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,132评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,147评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,106评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,019评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,862评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,301评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,521评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,682评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,405评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,996评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,651评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,803评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,674评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,563评论 2 352

推荐阅读更多精彩内容