public static boolean doSecKill(String uid, String proid)throws IOException {
// 1.uid和proid判断
if(uid == null||proid == null){
return false;
}
// 2.连接jedis
Jedis jedis = new Jedis("192.168.50.105",6379);
// 3. 拼接key
String invenKey = "sk:"+proid+"qt";
String userKey = "sk:"+proid+"qt";
// 4. 判断是否开始
String inven = jedis.get(invenKey);
if(inven == null){
System.out.println("秒杀尚未开始");
jedis.close();
return false;
}
// 5.判断是否抢过
if(jedis.sismember(userKey,uid)){
System.out.println("不可重复参与!");
jedis.close();
return false;
}
// 6.判断库存是否充足
if(Integer.parseInt(jedis.get(invenKey))<=0){
System.out.println("秒杀已结束,失败");
jedis.close();
return false;
}
// 进行库存扣除,添加成功人员名单
jedis.decr(invenKey);
jedis.sadd(userKey,uid);
System.out.println("秒杀成功");
jedis.close();
return true;
}
# 更新brew
$ brew update
# 安装 APR
$ brew install apr
# 安装 APR-UTIL (费时有些长)
$ brew install apr-util
# 安装 PCRE
$ brew install pcre
# 安装ab工具
$ brew install httpd-tools
# ab常用命令
$ ab -n 2000 -c 200 -k -p ~/postfile -T application/x-www-form-urlencoded url
# -n:请求数
# -c:并发数
# -k:开启 HTTP Keep-Alive,客户端在请求服务端的资源时,不会关闭与服务端的连接,
# -p:请求参数存放的文件
# -T:设置类型,就固定写: application/x-www-form-urlencoded
# url:替代为请求连接的url
解决超时问题:
连接池
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(100*1000);
poolConfig.setBlockWhenExhausted(true);
poolConfig.setTestOnBorrow(true); // ping PONG
jedisPool = new JedisPool(poolConfig, "192.168.50.105", 6379, 60000 );
}
}
}
return jedisPool;
}
public static void release(JedisPool jedisPool, Jedis jedis) {
if (null != jedis) {
jedisPool.close();
jedis.close();
}
}
}
解决超卖问题:
public class SecKill {
public static void main(String[] args) {
Jedis jedis = new Jedis();
}
public static boolean doSecKill(String uid, String proid) throws IOException {
// 1.uid和proid判断
if (uid == null || proid == null) {
return false;
}
// 2.连接jedis,使用连接池,避免超时
JedisPool jedisPool = new JedisPool();
Jedis jedis = jedisPool.getResource();
// 3. 拼接key
String invenKey = "sk:" + proid + "qt";
String userKey = "sk:" + proid + "qt";
// 监视库存
jedis.watch(invenKey);
// 4. 判断是否开始
String inven = jedis.get(invenKey);
if (inven == null) {
System.out.println("秒杀尚未开始");
jedis.close();
return false;
}
// 5.判断是否抢过
if (jedis.sismember(userKey, uid)) {
System.out.println("不可重复参与!");
jedis.close();
return false;
}
// 6.判断库存是否充足
if (Integer.parseInt(jedis.get(invenKey)) <= 0) {
System.out.println("秒杀已结束,失败");
jedis.close();
return false;
}
// multi事务操作
Transaction multi = jedis.multi();
multi.decr(invenKey);
multi.sadd(userKey, uid);
List<Object> result = multi.exec();
if (result == null || result.size() == 0) {
System.out.println("秒杀失败");
jedis.close();
return false;
}
System.out.println("秒杀成功");
jedis.close();
return true;
}
}
redis默认不能直接使用悲观锁,使用LUA脚本语言操作。
public class SecKill_redisByScript {
private static final org.slf4j.Logger logger =LoggerFactory.getLogger(SecKill_redisByScript.class) ;
public static void main(String[] args) {
JedisPool jedispool = JedisPoolUtil.getJedisPoolInstance();
Jedis jedis=jedispool.getResource();
System.out.println(jedis.ping());
Set<HostAndPort> set=new HashSet<HostAndPort>();
// doSecKill("201","sk:0101");
}
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" ;
static String secKillScript2 =
"local userExists=redis.call(\"sismember\",\"{sk}:0101:usr\",userid);\r\n" +
" return 1";
public static boolean doSecKill(String uid,String prodid) throws IOException {
JedisPool jedispool = JedisPoolUtil.getJedisPoolInstance();
Jedis jedis=jedispool.getResource();
//String sha1= .secKillScript;
String sha1= jedis.scriptLoad(secKillScript);
Object result= jedis.evalsha(sha1, 2, uid,prodid);
String reString=String.valueOf(result);
if ("0".equals( reString ) ) {
System.err.println("已抢空!!");
}else if("1".equals( reString ) ) {
System.out.println("抢购成功!!!!");
}else if("2".equals( reString ) ) {
System.err.println("该用户已抢过!!");
}else{
System.err.println("抢购异常!!");
}
jedis.close();
return true;
}
}
Redis 提供了2个不同形式的持久化方式。
dbfilename dump.rdb
dir ./
save 3600 1
:每隔3600秒有一个修改就会持久化一下save 20 3
然后启动redis之后40s左右时进行了3次修改,dump文件会立刻出现,因为40s时间早已达到20s的范围,然后立刻进行了三次修改,也满足了条件,所以立刻进行了持久化。而后我又紧跟着3次修改,之后立刻复制dump.rdb文件,关停redis服务,之后删除原dump文件,将我复制的dump备份文件更名为dump.rdb,之后发现其只保存了3次修改,紧随其后的3次修改需要再等20s才能进行持久化。redis-check-aof --fix appendonly.aof
命令可以进行文件修复info replication
打印自己信息slaveof <ip><port>
成为主机的从机slaveof no one
将从机变成主机redis-sentinel /myredis/sentinel.conf
private static JedisSentinelPool jedisSentinelPool=null;
public static Jedis getJedisFromSentinel(){
if(jedisSentinelPool==null){
Set<String> sentinelSet=new HashSet<>();
sentinelSet.add("192.168.11.103:26379");
JedisPoolConfig jedisPoolConfig =new JedisPoolConfig();
jedisPoolConfig.setMaxTotal(10); //最大可用连接数
jedisPoolConfig.setMaxIdle(5); //最大闲置连接数
jedisPoolConfig.setMinIdle(5); //最小闲置连接数
jedisPoolConfig.setBlockWhenExhausted(true); //连接耗尽是否等待
jedisPoolConfig.setMaxWaitMillis(2000); //等待时间
jedisPoolConfig.setTestOnBorrow(true); //取连接的时候进行一下测试 ping pong
jedisSentinelPool=new JedisSentinelPool("mymaster",sentinelSet,jedisPoolConfig);
return jedisSentinelPool.getResource();
}else{
return jedisSentinelPool.getResource();
}
}
setnx key value
只有在key为空时才可以赋值,所以设置值的同时上了锁del key
set key value nx ex time
设置值的同时设置了超时时间,ex后面的值就是超时时间@GetMapping("testLock")
public void testLock() {
//1获取锁,setne , 同时设置3秒过期,以避免中间出现异常,导致锁一直无法释放
Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", "111",3, TimeUnit.SECONDS);
//2获取锁成功、查询num的值
if (lock) {
Object value = redisTemplate.opsForValue().get("num");
//2.1判断num为空return
if (StringUtils.isEmpty(value)) {
return;
}
//2.2有值就转成成int
int num = Integer.parseInt(value + "");
//2.3把redis的num加1
redisTemplate.opsForValue().set("num", ++num);
//2.4释放锁,del
redisTemplate.delete("lock");
} else {
//3获取锁失败、每隔0.1秒再获取
try {
Thread.sleep(100);
testLock();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
set lock uuid nx ex 10
,然后用uuid进行判断。String uuid = UUID.randomUUID().toString();
//1获取锁,setne ,同时设置3秒过期,以避免中间出现异常,导致锁一直无法释放
Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid,3, TimeUnit.SECONDS);
...
//2.4释放锁,del
String lockUuid = (String)redisTemplate.opsForValue().get("lock");
if(uuid.equals(lockUuid)){
redisTemplate.delete("lock");
}
@GetMapping("testLockLua")
public void testLockLua() {
//1 声明一个uuid ,将做为一个value 放入我们的key所对应的值中
String uuid = UUID.randomUUID().toString();
//2 定义一个锁:lua 脚本可以使用同一把锁,来实现删除!
String skuId = "25"; // 访问skuId 为25号的商品 100008348542
String locKey = "lock:" + skuId; // 锁住的是每个商品的数据
// 3 获取锁
Boolean lock = redisTemplate.opsForValue().setIfAbsent(locKey, uuid, 3, TimeUnit.SECONDS);
// 第一种: lock 与过期时间中间不写任何的代码。
// redisTemplate.expire("lock",10, TimeUnit.SECONDS);//设置过期时间
// 如果true
if (lock) {
// 执行的业务逻辑开始
// 获取缓存中的num 数据
Object value = redisTemplate.opsForValue().get("num");
// 如果是空直接返回
if (StringUtils.isEmpty(value)) {
return;
}
// 不是空 如果说在这出现了异常! 那么delete 就删除失败! 也就是说锁永远存在!
int num = Integer.parseInt(value + "");
// 使num 每次+1 放入缓存
redisTemplate.opsForValue().set("num", String.valueOf(++num));
/*使用lua脚本来锁*/
// 定义lua 脚本
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
// 使用redis执行lua执行
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
redisScript.setScriptText(script);
// 设置一下返回值类型 为Long
// 因为删除判断的时候,返回的0,给其封装为数据类型。如果不封装那么默认返回String 类型,
// 那么返回字符串与0 会有发生错误。
redisScript.setResultType(Long.class);
// 第一个要是script 脚本 ,第二个需要判断的key,第三个就是key所对应的值。
redisTemplate.execute(redisScript, Arrays.asList(locKey), uuid);
} else {
// 其他线程等待
try {
// 睡眠
Thread.sleep(1000);
// 睡醒了之后,调用方法。
testLockLua();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
之前老版Redis想要搭集群需要单独安装ruby环境,Redis 5 将 redis-trib.rb 的功能集成到 redis-cli 。另外官方 redis-benchmark 工具开始支持 cluster 模式了,通过多线程的方式对多个分片进行压测。
Redis6新功能还有:
1、RESP3新的 Redis 通信协议:优化服务端与客户端之间通信
2、Client side caching客户端缓存:基于 RESP3 协议实现的客户端缓存功能。为了进一步提升缓存的性能,将客户端经常访问的数据cache到客户端。减少TCP网络交互。
3、Proxy集群代理模式:Proxy 功能,让 Cluster 拥有像单实例一样的接入方式,降低大家使用cluster的门槛。不过需要注意的是代理不改变 Cluster 的功能限制,不支持的命令还是不会支持,比如跨 slot 的多Key操作。
4、Modules API
Redis 6中模块API开发进展非常大,因为Redis Labs为了开发复杂的功能,从一开始就用上Redis模块。Redis可以变成一个框架,利用Modules来构建不同系统,而不需要从头开始写然后还要BSD许可。Redis一开始就是一个向编写各种系统开放的平台。