Redis列表(List)
- Redis 列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)。
- 它的底层实际是个双向链表,对两端的操作性能很高,通过索引下标的操作中间的节点性能会较差。
- 有序,元素可以重复
- lpush/rpush <key> <value1> <value2> <value3> .... 从左边/右边插入一个或多个值。
- lpop/rpop <key> 从左边/右边吐出一个值。值在键在,值光键亡。
- rpoplpush <key1> <key2> 从<key1>列表右边吐出一个值,插到<key2>列表左边。
- lrange <key> <start> <stop>
- lrange mylist 0 -1 从0开始,-1表示获取所有
- 按照索引下标获得元素(从左到右)
- lindex <key> <index> 按照索引下标获得元素(从左到右)
- llen <key> 获得列表长度
- linsert <key> before/after <value> <newvalue> 在<value>的前面/后面插入<newvalue>
- lrem <key> <n> <value> 从左边删除n个value(从左到右)
Redis集合(Set)
- 自动去重,无序,底层是value值为null的hash 表, 应用场景: 秒杀成功的用户列表
- 添加,删除,查找的复杂度都是O(1)。
- 一个算法,随着数据的增加,执行时间的长短,如果是O(1),数据增加,查找数据的时间不变
- sadd <key> <value1> <value2> .....
- 将一个或多个 member 元素加入到集合 key 中,已经存在的 member 元素将被忽略
- smembers <key> 取出该集合的所有值。
- sismember <key> <value> 判断集合<key>是否为含有该<value>值,有1,没有0
- scard <key> 返回该集合的元素个数。
- srem <key> <value1> <value2> .... 删除集合中的某个元素。
- spop <key> 随机从该集合中吐出一个值。(吐完以后集合中没有该元素)
- srandmember <key> <n> 随机从该集合中取出n个值。不会从集合中删除 。
- sinter <key1> <key2> 返回两个集合的交集元素。
- sunion <key1> <key2> 返回两个集合的并集元素。
- sdiff <key1> <key2> 返回两个集合的差集元素(key1中的,不包含key2中的)
Redis哈希(Hash)
- Redis hash 是一个键值对集合。
- Redis hash是一个string类型的field和value的映射表,hash特别适合用于存储对象。
- 类似Java里面的Map<String,Object>
- 主要有以下2种存储方式:
- 每次修改用户的某个属性需要,先反序列化改好后再序列化回去。开销较大。
- 通过 key(用户ID) + field(属性标签) 就可以操作对应属性数据了,既不需要重复存储数据,也不会带来序列化和并发修改控制的问题
- hset <key> <field> <value> 给<key>集合中的 <field>键赋值<value>
- hget <key1> <field> 从<key1>集合<field> 取出 value
- hmget <key> <field1> <field2> ... 获取hash结构的多个属性的属性值
- hmset <key1> <field1> <value1> <field2> <value2>... 批量设置hash的值
- hexists <key1> <field> 查看哈希表 key 中,给定域 field 是否存在。 存在1,不存在0
- hkeys <key> 列出该hash集合的所有field
- hvals <key> 列出该hash集合的所有value
- hincrby <key> <field> <increment> 为哈希表 key 中的域 field 的值加上增量 1 -1 increment表示增量多少,可为负数
- hsetnx <key> <field> <value> 将哈希表 key 中的域 field 的值设置为 value ,当且仅当域 field 不存在 .
-
java代码操作redis时,一般使用 string存储java对象,gson、fastjson 可以将java对象转为json字符串存入到redis中,获取时再还原为java对象
Redis有序集合Zset(sorted set)
-
与普通集合set非常相似,是一个没有重复元素的字符串集合。
- 不同之处是有序集合的每个成员都关联了一个评分(score),这个评分(score)被用来按照从最低分到最高分的方式排序集合中的成员。集合的成员是唯一的,但是评分可以是重复了 。
- 因为元素是有序的, 所以你也可以很快的根据评分(score)或者次序(position)来获取一个范围的元素。
- 访问有序集合的中间元素也是非常快的,因此你能够使用有序集合作为一个没有重复成员的智能列表。
- zadd <key> <score1> <value1> <score2> <value2>…
- 将一个或多个 member 元素及其 score 值加入到有序集 key 当中。
- zrange <key> <start> <stop> [WITHSCORES]
- 返回有序集 key 中,下标在<start> <stop>之间的元素
- 带WITHSCORES,可以让分数一起和值返回到结果集。
- zrangebyscore key min max [withscores] [limit offset count]
- 返回有序集 key 中,所有 score 值介于 min 和 max 之间(包括等于 min 或 max )的成员。有序集成员按 score 值递增(从小到大)次序排列。
- zrevrangebyscore key max min [withscores] [limit offset count]
- 同上,改为从大到小排列。
- zincrby <key> <increment> <value> 为元素的score加上增量
- zrem <key> <value> 删除该集合下,指定值的元素
- zcount <key> <min> <max> 统计该集合,分数区间内的元素个数
- zrank <key> <value> 返回该值在集合中的排名,从0开始。
Redis配置文件介绍
- 自定义目录:/myredis/redis.conf 使用vim 进行编辑查看
Units单位
- 配置大小单位,开头定义了一些基本的度量单位,只支持bytes,不支持bit
- 大小写不敏感
includes包含
- 类似jsp中的include,多实例的情况可以把公用的配置文件提取出来
网络相关配置
bind
- 默认情况bind=127.0.0.1只能接受本机的访问请求
- 不写的情况下,无限制接受任何ip地址的访问
- 生产环境肯定要写你应用服务器的地址;服务器是需要远程访问的,所以需要将其注释掉
- 如果开启了protected-mode,那么在没有设定bind ip且没有设密码的情况下,Redis只允许接受本机的相应
- 保存配置,停止服务,重启启动查看进程,不再是本机访问了。
protected-mode
- 将本机访问保护模式设置no
- 保护模式,和bind一起使用
Port
- 端口号,默认 6379
tcp-backlog
- 设置tcp的backlog,backlog其实是一个连接队列,backlog队列总和=未完成三次握手队列 + 已经完成三次握手队列。
- 在高并发环境下你需要一个高backlog值来避免慢客户端连接问题。注意Linux内核会将这个值减小到/proc/sys/net/core/somaxconn的值(128),所以需要确认增大/proc/sys/net/core/somaxconn和/proc/sys/net/ipv4/tcp_max_syn_backlog(128)两个值来达到想要的效果
timeout
- 一个空闲的客户端维持多少秒会关闭,0表示关闭该功能。即永不关闭。
tcp-keepalive
- 对访问客户端的一种心跳检测,每过n秒检测一次。
- 单位为秒,如果设置为0,则不会进行Keepalive检测,建议设置成60
GENERAL通用
daemonize
- 是否为后台进程,设置为yes
- 守护进程,后台启动
pidfile
- 存放pid文件的位置,每个实例会产生一个不同的pid文件
loglevel
- 指定日志记录级别,Redis总共支持四个级别:debug、verbose、notice、warning,默认为notice
- 四个级别根据使用阶段来选择,生产环境选择notice 或者warning
logfile
- logfile "" 代表不持久化保存日志
- 日志文件名称
databases 16
- 设定库的数量 默认16,默认数据库为0,可以使用SELECT <dbid>命令在连接上指定数据库id
SECURITY安全
设置密码
- 访问密码的查看、设置和取消
- 在命令中设置密码,只是临时的。重启redis服务器,密码就还原了。
- 永久设置,需要再配置文件中进行设置。
LIMITS限制
maxclients
- 设置redis同时可以与多少个客户端进行连接。
- 默认情况下为10000个客户端。
- 如果达到了此限制,redis则会拒绝新的连接请求,并且向这些连接请求方发出“max number of clients reached”以作回应。
maxmemory
- 建议必须设置,否则,将内存占满,造成服务器宕机
- 设置redis可以使用的内存量。一旦到达内存使用上限,redis将会试图移除内部数据,移除规则可以通过maxmemory-policy来指定。
- 如果redis无法根据移除规则来移除内存中的数据,或者设置了“不允许移除”,那么redis则会针对那些需要申请内存的指令返回错误信息,比如SET、LPUSH等。
- 但是对于无内存申请的指令,仍然会正常响应,比如GET等。如果你的redis是主redis(说明你的redis有从redis),那么在设置内存使用上限时,需要在系统中留出一些内存空间给同步队列缓存,只有在你设置的是“不移除”的情况下,才不用考虑这个因素。
maxmemory-policy
- volatile-lru:使用LRU算法移除key,只对设置了过期时间的键;(最近最少使用)
- allkeys-lru:在所有集合key中,使用LRU算法移除key
- volatile-random:在过期集合中移除随机的key,只对设置了过期时间的键
- allkeys-random:在所有集合key中,移除随机的key
- volatile-ttl:移除那些TTL值最小的key,即那些最近要过期的key
- noeviction:不进行移除。针对写操作,只是返回错误信息
-
# volatile-lru -> Evict using approximated LRU among the keys with an expire set. - 对所有的有过期时间的数据按照LRU算法移除 565 # allkeys-lru -> Evict any key using approximated LRU. - 使用LRU的策略对所有的键进行移除 566 # volatile-lfu -> Evict using approximated LFU among the keys with an expire set. - 对有过期时间的数据按照LFU的算法移除 567 # allkeys-lfu -> Evict any key using approximated LFU. 568 # volatile-random -> Remove a random key among the ones with an ex pire set. - 对有过期时间的键使用随机算法进行移除 569 # allkeys-random -> Remove a random key, any key. 570 # volatile-ttl -> Remove the key with the nearest expire time (min or TTL) - 对有过期时间的键,移除即将要过期的键 571 # noeviction -> Don't evict anything, just return an error on writ e operations. - 不做任何操作,当内存满了还有写操作时,直接返回一个错误 572 # 573 # LRU means Least Recently Used 最近最少使用(按时间) 离最近时间最远 574 # LFU means Least Frequently Used 使用次数最少(按次数) 最不常使用
maxmemory-samples
- 设置样本数量,LRU算法和最小TTL算法都并非是精确的算法,而是估算值,所以你可以设置样本的大小,redis默认会检查这么多个key并选择其中LRU的那个。
- 一般设置3到7的数字,数值越小样本越不准确,但性能消耗越小。
Redis_Jedis_测试
Jedis所需要的jar包
- commons-pool2-2.4.2.jar
- jedis-2.8.1.jar
连接Redis注意事项
- 禁用Linux的防火墙:Linux(CentOS7)里执行命令:systemctl stop/disable firewalld.service
- redis.conf中注释掉bind 127.0.0.1 ,然后 protected-mode no
- 创建maven项目
- 在pom中引入maven的依赖
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.6.1</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
</dependencies>
3. 创建java类,在main中测试redis的连接
package com.atguigu.jedis;
import redis.clients.jedis.Jedis;
public class JedisTest {
/**
* JedisConnectionException: java.net.SocketTimeoutException:
* 错误排查:
* 1.1 检查reids是否启动
* xhsell中: ps -aux|grep redis
* 1.2 检查虚拟机防火墙是否关闭
* 1.3 redis保护模式必须关闭
* vim /myredis/redis.conf
* 69行 , 注释bind 127.0.0.1
* 88行,protected-mode yes改为no
* 重启redis服务
*/
public static void main(String[] args) {
//java代码连接redis步骤
//1、获取连接
Jedis jedis = new Jedis("192.168.1.130", 6379);
//2、使用连接对象发送命名操作redis
String ping = jedis.ping();
System.out.println("ping = " + ping);
//3、关闭连接
jedis.close();
}
}
Redis_事务_锁机制_秒杀
Redis的事务定义
- Redis事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
- Redis事务的主要作用就是串联多个命令防止别的命令插队。
Multi、Exec、discard
- 从输入Multi命令开始,输入的命令都会依次进入命令队列中,但不会执行,直到输入Exec后,Redis会将之前的命令队列中的命令依次执行。
- 组队的过程中可以通过discard来放弃组队。
-
组队成功,提交成功
-
组队阶段保存,提交失败
-
组队成功,提交有成功,有失败
事务的错误处理
- 组队中某个命令出现了报告错误,执行时整个的所有队列都会被取消。
- 如果执行阶段某个命令报出了错误,则只有报错的命令不会被执行,而其他的命令都会执行,不会回滚。
悲观锁
悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。
- 悲观锁:请求获取到数据时会加锁,其他请求等待锁释放才可以争抢锁使用[高并发写操作为了保证数据安全可以使用, R数据库]
乐观锁
乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量。Redis就是利用这种check-and-set机制实现事务的。
- 乐观锁:对数据加版本号,当请求获取数据时会一起获取到版本号,其他请求读取数据也没有问题,当任意一个请求修改数据时,乐观锁会检查数据的版本号,如果请求数据的版本号和存储数据的版本号一致,可以修改,并提升版本号。如果获取了修改前数据版本号的其他请求再修改数据一定会失败[高并发读操作]
WATCH key[key...]
-
在执行multi之前,先执行watch key1 [key2],可以监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。
unwatch
- 取消 WATCH 命令对所有 key 的监视。
- 如果在执行 WATCH 命令之后, EXEC 命令或 DISCARD 命令先被执行了的话,那么就不需要再执行 UNWATCH 了。
Redis事务三特性
- 单独的隔离操作
- 事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
- 没有隔离级别的概念
- 队列中的命令没有提交之前都不会实际被执行,因为事务提交前任何指令都不会被实际执行
- 不保证原子性
- 事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚
案例分析
- java程序可以处理用户从程序的页面中提交的秒杀请求
- 项目中需要存储秒杀商品的数据,用户秒杀成功后,项目中也需要保存秒杀成功的用户
1、在redis中初始化秒杀的库存
在redis中:set sk:10001:qt 5
2、开发秒杀项目:
2.1 创建maven web工程[引入springmvc+spring依赖+commons-pool2+jedis]
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
<!-- 基础框架 -->
<spring.version>5.2.5.RELEASE</spring.version>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.6.1</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
<!-- 基础框架 -->
<!-- Spring配置 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${spring.version}</version>
</dependency>
</dependencies>
2.2 web.xml中配置
<!-- DispatcherServlet -->
<servlet>
<servlet-name>DispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath*:spring/springmvc.xml
</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>DispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
2.3 在resources下创建spring/springmvc.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:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!-- controller组件的扫描 -->
<context:component-scan base-package="com.atguigu.sk">
</context:component-scan>
<!-- 视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/pages/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
<!-- 静态资源放行的Servlet-->
<mvc:default-servlet-handler></mvc:default-servlet-handler>
<!-- 注解驱动 -->
<mvc:annotation-driven></mvc:annotation-driven>
</beans>
2.4 创建秒杀页面
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>尚筹网-秒杀页面</title>
<script src="jquery/jquery-2.1.1.min.js" type="text/javascript"></script>
</head>
<body>
<form action="sk/doSecondKill" method="post">
<input type="hidden" name="id" value="10001">
<a href="#">点击参与1元秒杀Iphone11promax</a>
</form>
<script type="text/javascript">
$("a").click(function () {
$.ajax({
type:"post",
url:$("form").prop("action"),
data:$("form").serialize(),
success: function (res) {
if(res=="ok"){
alert("秒杀成功");
}else{
alert(res);
$("a").prop("disabled" , true);
}
}
});
return false;
});
</script>
</body>
</html>
2.5 创建COntroller处理秒杀请求
@RestController //等价于 Controller+方法上的 ResponseBody
public class SecondKillController {
//sk/doSecondKill
@PostMapping(value = "/sk/doSecondKill",produces = "text/html;charset=UTF-8")
public String doSk(Integer id){
//随机生成用户id
Integer usrid = (int)(10000*Math.random());
//秒杀商品的id
Integer pid = id;
//秒杀业务
//拼接商品库存的key和用户列表集合的key
String qtKey = "sk:"+pid+":qt";
String usrsKey = "sk:"+pid+":usr";
Jedis jedis = new Jedis("192.168.1.130", 6379);
//1、判断该用户是否已经秒杀过
if(jedis.sismember(usrsKey, usrid + "")){
System.err.println("重复秒杀:"+usrid);
return "该用户已经秒杀过,请勿重复秒杀";
}
//2、获取redis中的库存,判断是否足够
String qtStr = jedis.get(qtKey);
if(StringUtils.isEmpty(qtStr)){
System.err.println("秒杀尚未开始");
return "秒杀尚未开始";
}
int qtNum = Integer.parseInt(qtStr);
if(qtNum<=0){
System.err.println("库存不足");
return "库存不足";
}
//3、库存足够,秒杀的业务
//减库存
jedis.decr(qtKey);
//将用户加入到秒杀成功的列表中
jedis.sadd(usrsKey , usrid+"");
System.out.println("秒杀成功:"+ usrid);
return "ok";
}
}
使用ab模拟高并发测试秒杀项目
1、初始化库存 :set sk:10001:qt 100
2、在xshell中使用ab高并发测试:http://主机ip地址:8888/SecondKill_war/sk/doSecondKill
http://192.168.1.1:8888/SecondKill_war/sk/doSecondKill
先创建postfile.txt文件:文件中保存要提交的请求参数
id=10001&
ab -n8000 -c500 -p postfile.txt -T application/x-www-form-urlencoded http://192.168.1.1:8888/SecondKill_war/sk/doSecondKill
高并发时多线程请求,有可能会导致超卖
利用事务解决超卖问题
每个请求再controller中被处理秒杀请求时,将多个命令组成队列,再对要使用的库存添加乐观锁
在使用库存前,对库存的键添加乐观锁,高并发访问时,每个请求使用数据都会携带版本。多个请求同时获取相同版本的数据,最多只能有一个修改。
@PostMapping(value = "/sk/doSecondKill",produces = "text/html;charset=UTF-8")
public String doSk(Integer id){
//随机生成用户id
Integer usrid = (int)(10000*Math.random());
//秒杀商品的id
Integer pid = id;
//秒杀业务
//拼接商品库存的key和用户列表集合的key
String qtKey = "sk:"+pid+":qt";
String usrsKey = "sk:"+pid+":usr";
Jedis jedis = new Jedis("192.168.1.130", 6379);
//1、判断该用户是否已经秒杀过
if(jedis.sismember(usrsKey, usrid + "")){
System.err.println("重复秒杀:"+usrid);
return "该用户已经秒杀过,请勿重复秒杀";
}
//2、获取redis中的库存,判断是否足够
//对库存的key 进行watch
//==================================================
jedis.watch(qtKey);
String qtStr = jedis.get(qtKey);
if(StringUtils.isEmpty(qtStr)){
System.err.println("秒杀尚未开始");
return "秒杀尚未开始";
}
int qtNum = Integer.parseInt(qtStr);
System.out.println("库存 = " + qtNum);
if(qtNum<=0){
System.err.println("库存不足");
return "库存不足";
}
//3、库存足够,秒杀的业务
//减库存
//======================================================
Transaction multi = jedis.multi();//开启redis的组队
multi.decr(qtKey);
//将用户加入到秒杀成功的列表中
multi.sadd(usrsKey , usrid+"");
multi.exec();
System.out.println("秒杀成功:"+ usrid);
jedis.close();
return "ok";
}
乐观锁导致的问题:
库存剩余的问题。多个并发的线程同时修改数据时,只能有一个修改成功
公平性的问题,先来的线程由于乐观锁可能会秒杀失败
利用lua脚本解决库存剩余问题
LUA脚本:小脚本语言
redis支持解析lua脚本,lua脚本可以将java代码中的业务逻辑和对redis的操作封装成一个lua脚本,一次性交给redis执行 ,lua脚本也有原子性,不会被打断
static String secKillScript = "local userid=KEYS[1];\r\n"
+ "local prodid=KEYS[2];\r\n"
+ "local qtkey='sk:'..prodid..\":qt\";\r\n"
+ "local usersKey='sk:'..prodid..\":usr\";\r\n"
+ "local userExists=redis.call(\"sismember\",usersKey,userid);\r\n"
+ "if tonumber(userExists)==1 then \r\n"
+ " return 2;\r\n"
+ "end\r\n"
+ "local num= redis.call(\"get\" ,qtkey);\r\n"
+ "if tonumber(num)<=0 then \r\n"
+ " return 0;\r\n"
+ "else \r\n"
+ " redis.call(\"decr\",qtkey);\r\n"
+ " redis.call(\"sadd\",usersKey,userid);\r\n"
+ "end\r\n"
+ "return 1";
@PostMapping(value = "/sk/doSecondKill",produces = "text/html;charset=UTF-8")
public String doSkByLUA(Integer id){
//随机生成用户id
Integer usrid = (int)(10000*Math.random());
Jedis jedis = new Jedis("192.168.1.130", 6379);
//加载LUA脚本
String sha1 = jedis.scriptLoad(secKillScript);
//将LUA脚本和LUA脚本需要的参数传给redis执行:keyCount:lua脚本需要的参数数量,params:参数列表
Object obj = jedis.evalsha(sha1, 2, usrid + "", id + "");
// Long 强转为Integer会报错 ,Lange和Integer没有父类和子类的关系
int result = (int)((long)obj);
if(result==1){
System.out.println("秒杀成功");
return "ok";
}else if(result==2){
System.out.println("重复秒杀");
return "重复秒杀";
}else{
System.out.println("库存不足");
return "库存不足";
}
}
//jedis连接池:
public class JedisPoolUtil {
private static volatile JedisPool jedisPool = null;
//私有构造器
private JedisPoolUtil(){}
public static JedisPool getJedisPoolInstance() {
if (null == jedisPool) {
synchronized (JedisPoolUtil.class) {
if (null == jedisPool) {
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxTotal(200);
poolConfig.setMaxIdle(32);
poolConfig.setMaxWaitMillis(1 * 1000);
poolConfig.setBlockWhenExhausted(true);
poolConfig.setTestOnBorrow(true);
jedisPool = new JedisPool(poolConfig, "192.168.1.130", 6379, 60000);
}
}
}
return jedisPool;
}
public static void release(JedisPool jedisPool, Jedis jedis) {
if (null != jedis) {
jedisPool.returnResource(jedis);
}
}
}
//使用
Jedis jedis = JedisPoolUtil.getJedisPoolInstance().getResource();
redis持久化
以快照集的形式 在一定时间间隔后保存到本地硬盘上
当redis服务端需要加载数据时直接可以加载到内存中
在redis.conf配置文件中,253行可以修改rdb持久化文件的名称
在263行可以修改redis自己创建文件的基准地址
设置持久化备份文件的存放位置
263 dir /myredis/
rdb备份的保存策略
218 save 900 1
219 save 300 10
220 save 60 10000
redis中执行命令时,如果执行save(同步)或者bgsave(异步),也可以触发redis使用rbd的方式将内存的数据写入到rdb文件
redis正常关闭时,也会自己调用bgsave完成数据的备份
flushdb也是写指令,如果触发redis的保存策略,也会覆盖dump.rdb文件
使用rbd文件防止误删除操作:
1、每隔一段时间对dump.rdb文件进行备份
cp dump.rdb dump-20-5-27.rdb.bak
2、如果redis中有误操作删除了redis中所有的数据
flushdb
3、借助备份rdb文件恢复误删除的数据
先停止redis服务
cp dump-20-5-27.rdb.bak dump.rdb
4、重启redis服务
rdb压缩保存,节省磁盘空间,文件不可读
235 stop-writes-on-bgsave-error yes 当bgsave执行时如果有错误停止写操作
241 rdbcompression yes 压缩保存数据
250 rdbchecksum no 检查数据完整性,比较消耗性能
rdb优点:
Ø 适合大规模的数据恢复
Ø 对数据完整性和一致性要求不高更适合使用
Ø 节省磁盘空间
Ø 恢复速度快