乐观锁

简介

加入版本标识,根据版本进行操作。 乐观锁适用于多读的应用类型,提高吞吐量。

Redis利用check set机制实现事务。

WATCH key

在执行multi前,先执行 watch key 监视一个/多个key,如果在执行事务之前,有key被其他命令改动,事务被打断

执行事务会返回(nil),事务没有执行。

watch [key1] [key2] ...

unwatch key

取消先前在key上设置的watch监视命令

unwatch [key1] [key2] ...

取消watch

Redis事务三特性:

  1. 单独的隔离操作


    事务中的所有命令都会序列化,按顺序的执行。在事务的执行过程中,不会被其他客户端发送来的命令所打断


  2. 没有隔离级别的概念


    队列中的任何命令在提交之前都不会实际执行,因为事务提交前任何指令都不会被实际执行


  3. 不保证原子性


    事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚。


## 悲观锁

### 简介:

在进行数据处理时会上锁。
传统的关系型数据库用到了 行锁,表锁等,读锁写锁等。
都是在操作之前先上锁

示例(乐观锁)

需要多次调用的方法:

@GetMapping("/SecKill/{uid}/{goods}")
    @ResponseBody
    public boolean doSecKill(@PathVariable("uid") String uid, @PathVariable("goods") String goods){
        System.out.println("uid: "+uid+" goods: "+goods);

        // 建立redis连接
        Jedis jedis = new Jedis("192.168.199.129",6379);
        // 1 uid和goods非空判断
        if(uid == null || goods ==null){
            return false;
        }
        // 2 库存key
        String stockKey = "sk:"+goods+":qt";
        // 3 秒杀成功用户key
        String userKey = "sk:"+goods+":user";
        // 4 获取库存,如果库存null,秒杀还没有开始
        String stock = jedis.get(stockKey);
        if(stock == 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(stock)<=0){
            System.out.println("秒杀已经结束了");
            jedis.close();
            return false;
        }
        // 7 秒杀过程
        // 库存-1
        jedis.decr(stockKey);
        // 把秒杀用户添加进清单中
        jedis.sadd(userKey,uid);
        System.out.println("秒杀成功了");
        jedis.close();
        return true;

    }

下载ab工具模拟高并发

yum install httpd-tools
ab 常用参数
    -n requests 请求次数
    -c concurrency 同时进行的次数
    -T content-type 提交模式{GET/POST...}
    -p postfile 文件,以post方式提交
// 向请求路径以-T标注的类型发送包含-p标注的文件夹共计1000次,一次100个请求。
ab -n 1000 -c 100 -p {路径/文件名} -T {类型} {请求路径}
// 模拟一次高并发
ab -n 500 -c 160 http://192.168.8.155:8080/redis/SecKill/1/2

高并发解决

连接超时问题:

往往是连接池问题,建立一个连接池类,先建立jedis连接池对象,再通过jedis连接池对象建立jedis对象

超卖问题:

需要添加乐观锁,监视多IO冲突。

// 监视库存
jedis.watch(StockKey);

// 添加事务
Transaction multi_ = jedis.multi();

// 命令加入事务
multi_.decr(StockKey);
multi_.sadd(userKey,uid);

// 执行
List<Object> results = multi_.exec();

// 判断
if(results == null || results.size()==0){
    sout(秒杀失败了);
    jedis.close();
    return false;
}

乐观锁库存遗留问题:

因为版本号不一致,失败。

使用脚本语言Lua Lua是一个小巧的脚本语言,解释器仅200k,适合作为嵌入式脚本语言。 来实现可配置性,可扩展性。

Lua 脚本可以将复杂的或者多步的redis操作,写为一个脚本,一次提交给redis执行,减少反复连接redis的次数。提升性能

通过lua脚本解决争抢问题,实际上就是利用了redis任务队列的方式解决多任务并发问题。

local userid=KEYS[1];
local goods=KEYS[2];
local qtkey="sk:"..goods..":qt";
local usersKey="sk:"..goods..":user";
local userExists = redis.call("sismember",userKey,userid);
if tonumber(userExists)==1 then
    // 表示已经秒杀过商品
    return 2;
end
......
Lua脚本是类似redis事务,有原子性。
但redis的lua脚本功能,只有在Redis 2.6以上的脚本才可以使用。

+Jmeter+

Jmeter对于并发测试,是更为专业的工具。
上一篇
下一篇