基于微信的投票系统,可谓是随处可见,每个人都应该看到多过,这样的活动。宝宝投票、最美校园投票、等等。各大机构,学校,企事业单位,都采用这种方式,评选。已达到推广,或者什么目的地吧。
目前,甲方想举办一个全国性活动,预计报名人数达到六万+,预估计,该活动期间 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
<span style="color:#000000">HSET 存储用户信息 eg:name : 张三,age:100,sex:1. $redis->HSET(hash_table,key,value); </span>
<span style="color:#000000">$redis->HSET(openid,name,张三); </span>
<span style="color:#000000">$redis->HSET(openid,age,100); </span>
读取: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="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.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系统,这些配置只够平时运行。到时候,要加配置。(代码能力有限,只能用钱解决)
我为人人,人人为我;美美与共,天下大同;