基于redis的高并发投票设计

版权声明:本文为博主原创文章,如若转载,请注明出处。 https://blog.csdn.net/qq_37837134/article/details/80151171

基于微信的投票系统,可谓是随处可见,每个人都应该看到多过,这样的活动。宝宝投票、最美校园投票、等等。各大机构,学校,企事业单位,都采用这种方式,评选。已达到推广,或者什么目的地吧。

目前,甲方想举办一个全国性活动,预计报名人数达到六万+,预估计,该活动期间 PV 量在 30w+.公司目前有做好的产品,但没有用Nosql,到时候肯定无法支持,所以,要重构。现有数据表中,报名量在 4千多,投票记录是3百多万,平均每人是741票。(存在刷票行为的记录)。项目中以openid作为用户的唯一标识,并且加入滑动验证码。但是模拟人为刷票,依然无法杜绝。唯一方法,就是写算法,判断用户的行为,但是,这样依然存在,如何界定该投票记录是否有效。所以,目前市场上的投票活动都明确标注,发现刷票,取消资格。

以上全部为废话。进入干货。

redis    五种数据类型

          1.string    key  ==>  value

          2.hash     hash_table    key1  ==>  value 1,key 2 ==>  value2,key3  ==>  value3  

          3.list       列表  lpush   rpop

          4 .set       集合

          5.Zset      有序集合        (可排名,依据分数权重)

运用到的数据类型:string   hash     list          Zset   四种

           1.hash    存储用户的信息,一个用户一个hash_table ,下边的昵称,头像,宣言等等,以键值对的形式存储,方便读取。

           2.list      运用队列,来做削峰处理,因为活动期间,肯定有很多人同时投票,投票记录首先插入队列,然后每分钟读取一次,批量写入数据库。批量写入要比一条一条的写入,效率高。

          3.Zset     有序集合,因为redis的有序集合,依据分数大小,来排序,可以方便实现排行榜。

          4.str       incr   自增,统计访问量

数据类型的具体应用

        一、hash   

HSET   存储用户信息   eg:name : 张三,age:100,sex:1.         $redis->HSET(hash_table,key,value);  
$redis->HSET(openid,name,张三);  
$redis->HSET(openid,age,100);  

读取:HGET  获取指定字段的值           HGETALL  获取所有字段的值

     二、list

         从微信哪里获取到,openid,sex,city,hearderimg, 然后投给谁,当前时间,json_encode($data),插入队列。

         $redis->lpush($list_table,$data);

        异步写入,每隔一分钟,批量写入

       $redis->rpop($list_table,$data);

   三,Zset  

       

$redis->zAdd($Zset_table,0,$username);      //插入集合
$redis->zIncrBy($Zset_table,1,$key);        //增量   修改权重
$redis->zCard($Zset_table);                 //计算   集合数量
$redis->zRevRange($Zset_table,$start,$end,'WITHSCORES');
注:从高到低排序,第一个参数   集合名                                必写

                                 第二个参数     起始位置    0 (索引)                              必写

                                 第三个参数     结束位置    -1  (最后一位)                     必写

                                 第四个参数     是否返回分数                                             选

下面上一个简单demo,   分页加载

rank.php

<?php
header("Content-type:text/html;charset=utf-8");
/*
*Function :rank.php;
*author  : 奔跑吧笨笨;
*date    : 2018/4/30;
*Info    :redis 排行榜
*/
//接收参数
$type = !empty($_POST['type']) ? $_POST['type'] : 0;

//实例化redis对象
$redis = new Redis();
$redis->connect('127.0.0.1',6379);
//有序集合表
$Zset_table = 'test_zset_redis';

$data = array();
if($type == 'all'){
    $data['status'] = 0;
    //获取参赛选手的信息
   if($redis->exists($Zset_table)){
       //页数
       $page = !empty($_POST['page']) ? $_POST['page'] : 0;
       $limit = 5;
       $start = $page * $limit;
       $end = (($page + 1) * $limit) - 1;

       //有序集合中的数量
       $num = $redis->zCard($Zset_table);
       if($num <= $end){
           $end = -1;
       }


       $data_info = $redis->zRevRange($Zset_table,$start,$end,'WITHSCORES');
       $data['status'] = 1;

       $i = 0;
      foreach($data_info as $k=>$v){
          $data['list'][$i]['scores'] = $v;
          $data['list'][$i]['key'] = $k;
          $data['list'][$i]['username'] = $k;
          $i++;
      }
   }
}elseif($type == 'add'){
    //参加选手
    $username = !empty($_POST['username']) ? $_POST['username'] : 0;
    if($username){
        //插入   有序集合
        $redis->zAdd($Zset_table,0,$username);
        $data['key'] = $username;
        $data['username'] = $username;
        $data['scores'] = 0;
    }
}elseif($type == 'support'){
    //投票 更新集合内该元素的权重
    if($redis->exists($Zset_table)){
        //投给谁  key === username
        $key = !empty($_POST['key']) ? $_POST['key'] : 0;
        if($key){
            //增量
            $redis->zIncrBy($Zset_table,1,$key);
            $data['status'] = 1;
        }
    }else{
        $data['status'] = 0;
    }
}

echo json_encode($data);

rank.html

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title>基于redis的排行榜</title>
    <link rel="stylesheet" type="text/css" href="http://cdn.bootcss.com/sweetalert/1.1.3/sweetalert.min.css"/>
</head>
<style>
    .th_css{
        margin: 2px;
        padding: 2px;
    }
    .rank_box{
        width: 20%;
        float: right;
        background-color: red;
        color: #fff;
    }
    ul li{
        list-style-type: none;
    }
    #rank_top{
        color: #ffff00;
    }
    .rank_info li>span{
        padding: 2px 15px;
        border-bottom: 2px solid transparent;
        height: 2.5rem;
    }
    .loader_more{
        display: none;
        cursor: pointer
    }
</style>
<body>
<center>
    <h1>基于redis的投票排行榜</h1>

    <div>
        <input type="text" name="username" class="username" placeholder="请输入用户名"/>
        <button id="btn">我要参赛</button>
    </div>


    <div>
        <h2>参赛选手</h2>
        <span><font color="red">* 点击选手,投票</font></span>

        <div class="join_rank">

        </div>
        <div class="loader_more">点击加载更多</div>
    </div>

    <div class="rank_box">
        <div>
            <span id="rank_top">排行榜</span>
        </div>
        <ul class="rank_info">

        </ul>
        <div class="loader_more">点击加载更多</div>
    </div>
</center>
</body>
<script src="../../Demo/month_2017/jquery-1.7.2.min.js"></script>
<script src="http://cdn.bootcss.com/sweetalert/1.1.3/sweetalert.min.js"></script>
<script>
    //初始化加载
    $(function(){
        var type = 'all';
        $.ajax({
            url:'./rank.php',
            data:{type:type},
            type:'POST',
            dataType:'json',
            success:function(data){
               if(data.status == 1){
                   //渲染  参赛选手
                   join_poster(data.list,0);
                   //右侧  排行榜
                   right_top(data.list,0);
               }else{
                   swal('', '暂无参加', 'warning');return false;
               }
            }
        })
    });
   //我要参赛
    $('#btn').click(function(){
        var username = $('.username').val();
        var type = 'add';

        if(username == ''){
            swal('', '请输入用户名', 'warning');return false;
        }else{
            $.ajax({
                url:'./rank.php',
                data:{username:username,type:type},
                type:'POST',
                dataType:'json',
                success:function(msg){
                    var html_data = '<th class="th_css"><button class="vote" attr_key="'+msg.key+'">'+msg.username+' '+msg.scores+'分</button></th>';
                 $('.join_rank').append(html_data);
            }
            })
        }
    });
    //我要投票
   $(document).on('click','.vote',function() {
       var type = 'support';
       var key = $(this).attr('attr_key');
       $.ajax({
           url: './rank.php',
           data: {type: type, key: key},
           type: 'POST',
           dataType: 'json',
           success: function (data) {
               if (data.status == 1) {
                   //投票成功
                   swal({
                               title: "",
                               text: "恭喜你,投票成功",
                               type: "success",
                               confirmButtonColor: "#DD6B55",
                               confirmButtonText: "OK",
                               closeOnConfirm: false
                           },
                           function(){
                               location.reload();
                           });
               } else {
                   swal('', '投票失败', 'warning');
                   return false;
               }
           }
       })
   });
    //点击加载更多
    var page = 1;
    $('.loader_more').click(function(){
        var type = 'all';
        $.ajax({
            url:'./rank.php',
            data:{type:type,page:page},
            type:'POST',
            dataType:'json',
            success:function(data){
                if(data.status == 1){
                    //渲染  参赛选手
                    join_poster(data.list,1);
                    //右侧  排行榜
                    right_top(data.list,1);

                    page++;
                }else{
                    swal('', '暂无参加', 'warning');return false;
                }
            }
        })
    });


    //参赛选手的渲染
    function join_poster(data,is_more){
        var poster_len = data.length;
        if(poster_len > 5){$('.loader_more').show();}else{ $('.loader_more').hide();};
            var str = '';
            for(var i = 0;i< poster_len;i++){
                str += '<th class="th_css"><button class="vote" attr_key="'+data[i].key+'">'+data[i].username+' '+data[i].scores+'分</button></th>';
            }
            var _this =  $('.join_rank');
        if(is_more == 0){
            _this.empty();
        }
            _this.append(str);

    }

    var posterlilen = $(".poster_li").length;   //排行榜当前列表总长度
    //右侧最新榜
    function right_top(data,is_more){
        var poster_len = data.length;
        if(poster_len >= 5 ){ $('.loader_more').show();}else{ $('.loader_more').hide();};

            var str = '';
            for(var i = 0;i< poster_len;i++){
                str +='  <li>';
                str +=' <span>'+parseInt(i + 1 +posterlilen)+'</span>';
                str +=' <span>'+data[i].username+'</span>';
                str +=' <span>'+data[i].scores+'票</span>';
                str +=' </li>';
            }
            var _this =  $('.rank_info');
        if(is_more == 0){
            _this.empty();
        }
            _this.append(str);
        posterlilen+= poster_len;

    }
</script>
</html>

样式很丑,但简单的功能实现了。(麻雀虽小,五脏俱全)。

 注意:若为生产环境,需要预防雪崩,穿刺等问题,做好nosql的缓存,读取nosql数据失败,查询DB.


目前,正式代码正在编写当中,关于上线之后的状态,在活动结束之后,再总结更新。

架构配置:2台服务器    阿里云

                 mysql数据库   阿里云主从

                 redis              阿里云主从

这是目前的配置,因为,公司做的saas系统,这些配置只够平时运行。到时候,要加配置。(代码能力有限,只能用钱解决)



我为人人,人人为我;美美与共,天下大同;



没有更多推荐了,返回首页