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