在SSM中使用Redis实现高并发抢座,写了大概需要的过程,大家可以借鉴借鉴
在SSM中使用Redis实现高并发抢座
一、 Redis的配置 Maven配置:
1 2 3 4 5 6 7 8 9 10 11 <dependency > <groupId > redis.clients</groupId > <artifactId > jedis</artifactId > <version > 2.9.0</version > </dependency > <dependency > <groupId > org.springframework.data</groupId > <artifactId > spring-data-redis</artifactId > <version > 1.8.4.RELEASE</version > </dependency >
spring-redis.xml配置文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 <?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:p ="http://www.springframework.org/schema/p" xmlns:context ="http://www.springframework.org/schema/context" xmlns:cache ="http://www.springframework.org/schema/cache" xsi:schemaLocation ="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache-4.2.xsd" > <bean id ="redisProperties" class ="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer" > <property name ="locations" > <list > <value > classpath:redis.properties</value > </list > </property > <property name ="ignoreUnresolvablePlaceholders" value ="true" /> </bean > <bean id ="poolConfig" class ="redis.clients.jedis.JedisPoolConfig" > <property name ="maxIdle" value ="$ {redis.maxIdle}" /> <property name ="maxWaitMillis" value ="$ {redis.maxWait}" /> <property name ="testOnBorrow" value ="$ {redis.testOnBorrow}" /> </bean > <bean id ="jedisConnectionFactory" class ="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" p:host-name ="$ {redis.host}" p:port ="$ {redis.port}" p:password ="$ {redis.pass}" p:pool-config-ref ="poolConfig" /> <bean id ="redisTemplate" class ="org.springframework.data.redis.core.StringRedisTemplate" > <property name ="connectionFactory" ref ="jedisConnectionFactory" /> <property name ="keySerializer" > <bean class ="org.springframework.data.redis.serializer.StringRedisSerializer" /> </property > <property name ="hashKeySerializer" > <bean class ="org.springframework.data.redis.serializer.StringRedisSerializer" /> </property > <property name ="valueSerializer" > <bean class ="org.springframework.data.redis.serializer.StringRedisSerializer" /> </property > <property name ="hashValueSerializer" > <bean class ="org.springframework.data.redis.serializer.StringRedisSerializer" /> </property > </bean > </beans >
redis.properties文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 redis.host =127.0.0.1 redis.port =6379 redis.pass =redis.database =0 redis.maxIdle =300 redis.maxWait =3000 redis.testOnBorrow =true redis.charset =GBK
二、 在服务器启动前加载用户信息到Redis (一)、此处实现了在服务器启动之前先加载用户信息到Redis,这里提供实现方法: 1.新建初始化类并完成加载代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import javax.servlet.http.HttpSessionEvent;import javax.servlet.http.HttpSessionListener; public class Init implements HttpSessionListener { static { } @Override public void sessionCreated (HttpSessionEvent arg0) {} @Override public void sessionDestroyed (HttpSessionEvent arg0) {} }
2.在web.xml配置类路径
1 2 3 <listener > <listener-class > edu.options.init.Init</listener-class > </listener >
(二)、实现思路 一、使用JDBC查询用户信息的用户名密码/密码MD5值(两个字段可以作为键值),此处不再赘述。 二、使用Jedis加载到Redis(此处提供简单的操作): 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 try { Jedis jedis = new Jedis ("127.0.0.1" , 6379 ); jedis.flushAll(); for (int i = 1 ; i <= SEAT_SIZE; i++) { jedis.hset("options_" + i, "stock" , "1" ); jedis.hset("options_" + i, "unit_amount_" + i, "1" ); } jedis.hset("options_all" , "all" , SEAT_SIZE + "" ); Map<String, String> numberAndNames = getStudents(); Set<String> s = numberAndNames.keySet(); for (String number : s){ String name = numberAndNames.get(number); jedis.hset(number, "DNUI" , name); } String[] times = Tools.getFile("resource/time.csv" ).split("," ); jedis.hset("startTime" , "TIME" , times[0 ]); jedis.hset("endTime" , "TIME" , times[1 ]); jedis.hset("createTime" , "TIME" , times[2 ]); } catch (Exception e) { e.printStackTrace(); }
这里我存储的是学生学号及姓名。
三、具体调用 (一)、此处实现了从resources文件夹出导入Lua脚本,这样可以方便编辑并可以方便以后的改动。这里提供一个函数,可以实现从资源文件夹加载文件(部署到服务器上也可以受用): 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public static String getFile (String fileName) { InputStream inputStream = Tools.class.getClassLoader().getResourceAsStream(fileName); ByteArrayOutputStream result = new ByteArrayOutputStream (); byte [] buffer = new byte [1024 ]; int length; String str = "" ; try { if (inputStream != null ) { while ((length = inputStream.read(buffer)) != -1 ) { result.write(buffer, 0 , length); } inputStream.close(); str = result.toString(StandardCharsets.UTF_8.name()); } } catch (IOException e) { e.printStackTrace(); } return str; }
使用时可以这样获取Lua代码:
1 String luaCode = getFile(“resource/options.lua”);
(二)、Lua文件的编写: 1 2 3 4 5 6 7 8 9 10 11 12 local listKey = 'options_list_' ..KEYS[1 ]local options = 'options_' ..KEYS[1 ]local stock = tonumber (redis.call('hget' , options, 'stock' ))if stock <= 0 then return 0 end stock = stock -1 redis.call('hset' , options, 'stock' , tostring (stock)) redis.call('rpush' , listKey, ARGV[1 ]) local all = tonumber (redis.call('hget' , 'options_all' , 'all' ))all = all -1 redis.call('hset' , 'options_all' , 'all' , tostring (all)) if all == 0 then return 2 end return 1
这是我参考一个抢票系统改的,有不足请指出
(三)、Lua文件的使用: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 @Autowired private RedisTemplate redisTemplate;private static final String SCRIPT = Tools.getFile("lua/options.lua" );private String SHA1 = null ;public String options (long seatNumber, long number) { String args = number + "-" + System.currentTimeMillis(); Long result; Jedis jedis = (Jedis) redisTemplate.getConnectionFactory().getConnection().getNativeConnection(); try { if (SHA1 == null ) { SHA1 = jedis.scriptLoad(SCRIPT); } Object res = jedis.evalsha(SHA1, 1 , seatNumber + "" , args); result = (Long) res; if (result == 2 ){ SimpleDateFormat format = new SimpleDateFormat ("yyyy-MM-dd HH:mm:ss" ); String nowTime = format.format(new Date ()); jedis.hset("endTime" , "TIME" , nowTime); this .redisOptionsService.saveRedisOptions(); } if (result > 0 ){ jedis.del(number + "" ); SimpleDateFormat df = new SimpleDateFormat ("yyyy-MM-dd HH:mm:ss:SSS" ); System.out.println( "学号为" + number + "的同学在" + df.format(new Date ()) + "的时间抢到" + seatNumber + "座位" ); return "成功占据座位" ; } } finally { if (jedis != null && jedis.isConnected()) { jedis.close(); } } return "此座位已被占据" ; }
(四)、抢座完成后的保存:可以看到当抢座完成后会调用this.redisOptionsService.saveRedisOptions();进行数据持久化存储。 这里是大部分实现代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 @Autowired private RedisTemplate redisTemplate = null ; @Async public void saveRedisOptions () { System.err.println("开始保存数据" ); Long start = System.currentTimeMillis(); List<Seat> seatList = new ArrayList <>(); for (long i = 1 ; i <= SEAT_NUM; i++) { seatList.add(getSeatByRedis(i)); } int count = executeBatch(seatList); Long end = System.currentTimeMillis(); System.err.println("保存数据结束,耗时" + (end - start) + "毫秒,共" + count + "条记录被保存。" ); } @SuppressWarnings("unchecked") private Seat getSeatByRedis (Long seatId) { BoundListOperations ops = redisTemplate.boundListOps(PREFIX + seatId); List userIdList = ops.range(0 , 1 ); String args = userIdList.get(0 ).toString(); String[] arr = args.split("-" ); long numberId = Long.parseLong(arr[0 ]); long time = Long.parseLong(arr[1 ]); Seat seat = new Seat (); seat.setSeatnumber(seatId); seat.setNumber(numberId); seat.setTime(new Timestamp (time)); User user = this .userDao.findByNumber(numberId); seat.setName(user.getName()); seat.setDepartment(user.getDepartment()); seat.setSex(user.getSex()); return seat; }
__注意__:saveRedisOptions()前使用了@Async注解进行异步调用来不阻塞响应。
executeBatch()是个调用数据库存储的函数。
此为博主副博客,留言请去主博客,转载请注明出处: https://www.baby7blog.com/myBlog/15.html