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

3,310 阅读15分钟

场景分析

在考虑详细设计之前,我们先意淫一下秒杀场景,挖掘出场景具备的一些特性:
➟系统、数据场景特性
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原理图:

由于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>