http://segmentfault.com/a/1190000000495903
实时投票系统:数据类型上的差异:memcache和redis
实战--积分投票系统中的细节:
实时投票系统:数据类型上的差异:memcache和redis
最近要写一个类投票的系统:由于访问量可能会比较大,最好不直接使用mysql数据库,完全使用缓存的话,存在缓存失效等的风险,因此在mysql上面写个缓存中间过渡:
1.memcache
1.memcache
按照我的习惯,肯定是使用redis,但是公司目前这个项目线上还没有redis服务,只好凑合一下使用memcache。
第一次的时候使用memcache
这么写:
key为'13453' value为13920,
key为'13453' value为13920,
key为'13454' value为12989,
...
...
但是这么一来的话就不好排序了,每次想排序,都要从缓存中取出很多个值。
第二次这么写:
memcache中存储了一个数组: 'pid' =>count
array(
'13453' =>13920,
'13454'=>12989,
...
)
每次有人投票需要先取出该数组,然后再对应的pid上面加1,排序,再存入memcache中,代码如下:
每次有人投票需要先取出该数组,然后再对应的pid上面加1,排序,再存入memcache中,代码如下:
[php] view plaincopyprint?在CODE上查看代码片派生到我的代码片
/**
* @brief 从memcache中取出数据,并相应增加一,然后排序
*/
private function _addCount($uid) {
if(empty($uid)) {
return false;
}
$mc_key = "MEM_KEY_SORT";
$list = self::memcache()->get($mc_key);
$list[$uid]++;
if(empty($list)) {
$list = $this->_getCount();//数据不存在则查询数据库(group by)
}
asort($list);
$ret = self::memcache()->set($mc_key, $list);
}
[php] view plaincopyprint?在CODE上查看代码片派生到我的代码片
/**
* @brief 从数据库获取投票次数
* @return array('uid' => '次数')
*/
private function _getCount() {
$sql = "select pid,count(pid) as count from
$query = self::db()->query($sql);
while(($row = self::db()->fetch_array($query))!==FALSE) {
$list[$row['pid']] = $row['count'];
}
return $list;
}
/**
* @brief 从memcache中取出数据,并相应增加一,然后排序
*/
private function _addCount($uid) {
if(empty($uid)) {
return false;
}
$mc_key = "MEM_KEY_SORT";
$list = self::memcache()->get($mc_key);
$list[$uid]++;
if(empty($list)) {
$list = $this->_getCount();//数据不存在则查询数据库(group by)
}
asort($list);
$ret = self::memcache()->set($mc_key, $list);
}
[php] view plaincopyprint?在CODE上查看代码片派生到我的代码片
/**
* @brief 从数据库获取投票次数
* @return array('uid' => '次数')
*/
private function _getCount() {
$sql = "select pid,count(pid) as count from
tmp_ssjj_zqwz
group by pid";$query = self::db()->query($sql);
while(($row = self::db()->fetch_array($query))!==FALSE) {
$list[$row['pid']] = $row['count'];
}
return $list;
}
数据结构如下:
CREATE TABLE
PRIMARY KEY (
UNIQUE KEY
KEY
) ENGINE=MyISAM AUTO_INCREMENT=8 DEFAULT CHARSET=gbk;
加上索引,使用group by来统计数据:
tmp_ssjj_zqwz
(id
int(11) NOT NULL AUTO_INCREMENT COMMENT '自增编号',uid
int(11) NOT NULL COMMENT '投票id',type
int(11) NOT NULL DEFAULT '0' COMMENT '投票类型',pid
int(11) NOT NULL DEFAULT '0' COMMENT '被投票id',PRIMARY KEY (
id
),UNIQUE KEY
unique_uid_type
(uid
,type
),KEY
index_pid
(pid
)) ENGINE=MyISAM AUTO_INCREMENT=8 DEFAULT CHARSET=gbk;
加上索引,使用group by来统计数据:
select pid,count(pid) as count from
tmp_ssjj_zqwz
group by pid
2.使用redis中的有序集合,直接解决问题:
value为'13453' score为13920,
value为'13454' score为12989,
...
一段代码,直接搞定:
一段代码,直接搞定:
$list= $redis->zRange($_GET['key'], 0, -1);
最后,我是redis的fans。比较支持redis
http://segmentfault.com/a/1190000000510605实战--积分投票系统中的细节:
好几天没有写博客了,一直忙这写这个积分投票-兑换礼包系统.有很多血泪的教训来分享下:
之前,我一直是写手机接口的,跟前端基本上没有交集,即使有也是给内部提供管理平台,这次可以说给我上了一堂课:
1.实时性:大量的操作是建立在memcache缓存的基础上的,mysql数据库是为了提供数据持久性和记录日志的.因此查询之前会先访问缓存,如果缓存不存在或者失效,去访问数据库.
2.数据量比较大:数据库的索引没有建立到最好.
3.权限的问题,不是所有人都拥有权限可以投票,于是我把拥有权限的用户id放入了一个数组中并放在缓存,同步到数据库里面,最后发现这次投票人数特别多,每次查询一个用户的权限,需要把所有人的权限都拿出来,使用in_array(),来判断,主管发现后,果断改啊,每个用户使用独立的key来保存是否拥有权限.
4.安全性:从前端传递过来的参数要假设是不可靠的,在兑换礼包的时候,我从前端传递过来了礼包的类型和要扣除的积分,放入数据库中,哎!首先悲剧的就是要扣除的积分不能是传递过来的,而是根据礼包的类型来判断.并且要把礼包的类型做出范围配置来检查是否合法.刚写完程序的时候,主管看了看然后请求了一个url: www.example.com/?type=6&score=1,本来第六个礼包要扣除积分80个,结果现在只扣除1个积分用户就领取到了第六个礼包.然后要判断礼包只有六中type应该大于1,小于7.写成配置文件来判断如下:
之前,我一直是写手机接口的,跟前端基本上没有交集,即使有也是给内部提供管理平台,这次可以说给我上了一堂课:
1.实时性:大量的操作是建立在memcache缓存的基础上的,mysql数据库是为了提供数据持久性和记录日志的.因此查询之前会先访问缓存,如果缓存不存在或者失效,去访问数据库.
2.数据量比较大:数据库的索引没有建立到最好.
3.权限的问题,不是所有人都拥有权限可以投票,于是我把拥有权限的用户id放入了一个数组中并放在缓存,同步到数据库里面,最后发现这次投票人数特别多,每次查询一个用户的权限,需要把所有人的权限都拿出来,使用in_array(),来判断,主管发现后,果断改啊,每个用户使用独立的key来保存是否拥有权限.
4.安全性:从前端传递过来的参数要假设是不可靠的,在兑换礼包的时候,我从前端传递过来了礼包的类型和要扣除的积分,放入数据库中,哎!首先悲剧的就是要扣除的积分不能是传递过来的,而是根据礼包的类型来判断.并且要把礼包的类型做出范围配置来检查是否合法.刚写完程序的时候,主管看了看然后请求了一个url: www.example.com/?type=6&score=1,本来第六个礼包要扣除积分80个,结果现在只扣除1个积分用户就领取到了第六个礼包.然后要判断礼包只有六中type应该大于1,小于7.写成配置文件来判断如下:
/**
* @brief 将礼包对应扣除的积分写成配置,不要相信用户提交的数据,
* @return array();
*/
public function getTypes() {
return array(
11 => 5, //礼包1对应扣除的积分5,同下
12 => 10,
13 => 20,
14 => 30,
15 => 50,
16 => 80
);
}
5.防止刷积分的处理:如果用机器大量快速请求,有可能出现服务器挂掉,且出现一个人领取多个礼包,因此将用户id:uid和礼包类型:type做唯一索引:UNIQUE KEY
在memcache中存入一个1s过期的key,保证一个uid每秒只能领取一次礼包.
unique_uid_type
(uid
,type
),在memcache中存入一个1s过期的key,保证一个uid每秒只能领取一次礼包.
6.一些非实时的数据,采用数据查询时从缓存中读取,不存在再从数据库读取;增删改时清除该key的缓存.下次从数据库读取.结果由于只改变了查询的时候的缓存的key,没有改 增删改 数据时缓存的key,导致数据全部从数据库中读取..因此一定要保证,写入缓存和读取缓存的key保持一致.否则...
7.由于有很多页面,需要共享用户的信息,而用户信息需要经过处理,就把用户的信息放到了__construct()里面,也就是说所有的页面都会处理用户信息,结果有几个页面是不使用用户信息的...
8.命名的规范:对缓存命名时必须遵循团队规范,否则可能会和其他开发人员发生缓存key冲突.
9.特殊字符的编码处理:用户名是utf-8编码的,而程序中是gbk编码的,结果转码后发现,用户名凌乱了.处理办法:copy到txt文档中使用记事本打开,重新复制一遍,很多的用户名中会出现特殊字符比如双引号等,使用:htmlspecialchars();
10.数据表中增加一个create_time,用来记录时间排除bug
11.缓存设置的时间要根据具体的业务逻辑来设计,比如下面的场景:用户投票和兑换积分一般时间不会超过十分钟,因此,一些非实时的缓存的过期时间设置成10分钟就够了
12.将所有权限的检测封装到一个函数中统一处理,是代码更简洁方便
13.json_encode()不能处理gbk编码:json_encode('gbk','utf-8',$array);