Laravel 实现二级缓存 提高缓存的命中率和细粒化缓存 key

  缓存在web应用中有着很重要的地位,应用比较广范。传统的缓存使用方式有两种方式:
  1.先写数据库后写缓存。
  2.先写缓存后将sql写队列批量执行。
  后一种方式明显比上一种方式 执行效率要高  提高了应用的qps  
  但是第一种方式虽然牺牲了一些性能但是保证了数据的一致性,后一种方式在数据不一致性要做一些措施。
  本人使用的lumen 框架 首先感谢你使用laravel或者lumen 框架 因为你正在用Java的方式 写php laravel优雅迷人,代码格式比较风骚, 她是那样迷人有魅力 ,就像十八岁的姑娘浑身散发着迷人青春的信息。
  下面先说一下本人应用结构 本人公分四层结构 
  1.model层  维护数据表结构 表与表之间的关联关系 类似于spring和 symfony的Entity;
  2.repository层 辅助model层对维护的数据表进行curd操作 
  3.service层 辅助controller 实现业务逻辑 
  4.controller 负责接收参数 service 调度
  本人使用的缓存双写的第一种方式 即先入数据库后入缓存的方式 
 model层:
namespace App\Models\Platform;
use Illuminate\Database\Eloquent\SoftDeletes;
class HotWordModel extends BaseModel
{
//设置软删除
use SoftDeletes;
// 定义伪删除字段名称
const DELETED_AT = 'delete_time';
// 表名
protected $table = 'hotword';
// 表主键
protected $primaryKey = 'id';

// 不可被批量赋值的属性
protected $guarded = [];

// 不启用时间自动维护,
public $timestamps = false;

// 可以被批量赋值的属性。
protected $fillable = ['id', 'title', 'sort', 'is_show','create_uid','create_time','update_time','update_uid','delete_time'];

/**
* 添加商品 详细数据
* @param $data array 商品详细数据
*/
public function add($data)
{
$data['create_uid'] = $this->loginUser['id'];
$data['create_time']= $data['update_time'] = time();
return $this->create($data);
}

/**
* 编辑商品 详细数据
* @param $goodsId int 要编辑的商品id
* @param $data  array 商品数据
*/
public function edit($goodsId, $data)
{
$data = array_only($data, $this->fillable);         //字段过滤
unset($data['id']);    //避免id 注入
return $this->where($this->primaryKey, '=', $goodsId)->update($data);
}

/**
* 通过商品id集合批量删除
* @param $goodsIds
* @return mixed
*/
public function deleteByGoodsIds($goodsIds)
{
    return $this->whereIn('id', $goodsIds)->delete();
}

public function getTableName(){
    return $this->table;
}

public function getPrimaryKey(){
    return $this->primaryKey;
}

public function getFillAble(){
    return $this->fillable;
}
model层其实按照正常的逻辑是没有数据库操作的 
缓存处理逻辑 本人使用的是缓存门面 
cache层:
namespace App\Cache\Platform;
use App\Common\Base\BaseSingleton;
use Illuminate\Support\Facades\Cache;
use App\Constants\Nomal\Consistent;

class HotWordCache extends BaseSingleton
{
    protected $data;

/**
 * 缓存key 前缀
 * @var string
 */
public static $cacheKeyPx;

/**
 * 缓存类型
 * @var string
 */
public static $cacheType = 'goods';     //缓存连接(对应config/cache.php 文件中)

/**
 * @var int 缓存过期时间
 */
protected static $cacheExpire = 0;     //0为永久保存,>0 为设置时间 (分钟数)

private static $consistent;
// 构造函数注入hash
public function __construct(Consistent $consistents)
{
    self::$consistent = $consistents;
    self::setHashKey(array('goods'));

}

//获cache
static public function getCache($cacheName)
{
    return Cache::store(self::getHashValue($cacheName))->get($cacheName);
}
//写cache
static public function setCache($cacheName, $value, $expire_time = NULL)
{
    $expire_time =  strtotime(date('Y-m-d')) + 97200 - time();
    if($expire_time<0){
        Cache::store(self::getHashValue($cacheName))->forever($cacheName, $value);
    }else{
        Cache::store(self::getHashValue($cacheName))->put($cacheName, $value,$expire_time);
    }
}
//清cache
static public function delCache($cacheName)
{
    if (static::$cacheExpire <= 0) {     //删除永久保存
        Cache::store(self::getHashValue($cacheName))->forget($cacheName);
    } else {
        Cache::store(self::getHashValue($cacheName))->pull($cacheName);
    }
}
 // 获取缓存种子
static public function getCacheSeed($key)
{
    $result = self::getCache($key);
    if (!empty($result)) return $result;
    return self::setCacheSeed($key);
}
// 设置缓存种子
static public function setCacheSeed($key, $timesec = 0)
{
    $expire_time = 0<$timesec? $timesec: strtotime(date('Y-m-d')) + 97200 - time();
    $data = uniqid();
    if(self::setCache($key, uniqid(), $expire_time)) {
        return $data;
    } else {
        return false;
    }
}
// 分布式锁的实现
static public function getCacheLock($key){
    Cache::lock($key, 10)->block(5, function ($key) {
        return $key;
    });
}
// 传入需要hash的key
static public function setHashKey(array $Hashkey){
     if(!empty($Hashkey) && is_array($Hashkey)){
         foreach($Hashkey as $value){
             self::$consistent->addNode($value);
         }
     }
}
// 返回一致性hash后的value
static public function getHashValue(string $hashValue){
    return self::$consistent->lookup($hashValue);
}
// 设置二级缓存
static public function setSecondCache(string $cacheName,string $value,int $time = 86400){
    $expire_time = strtotime(date('Y-m-d')) + $time - time() + rand(1,1000);
    Cache::store(self::getHashValue($cacheName))->put($cacheName,$value,$expire_time);
}
// 获取二级缓存
static public function getSecondCache(string $cacheName){
    return Cache::store(self::getHashValue($cacheName))->get($cacheName);
}
// 返回一级缓存的key
static public function getFirstCacheKeyById($onlyKey,$className,$id){
    return $onlyKey.'_'.md5(serialize($className)).'_'.$id;
}
}   
    缓存主要针对于传统的一致性哈希算法将需要入缓存的数据存到某个缓存数据库(本人目前使用一致性hash),现在redis 高版本支持Cluster集群(3.0以后)比一致性哈希算法更为优秀, 你们抽空可以看一下 laravel对redis 集群支持也比较优秀 ,深感技术更新比较快 ,朕的大清药丸了。
  下面讲一下 本人二级缓存的实现思路: 业务场景 维护一个表的缓存数据 ,多条件查询各种分页的查询出来的数据这些, 常规做法 ,每一个查询都针对于查询条件和分页维护一个新的缓存key, 我之前写的逻辑 ,一旦数据表里的数据 ,有更新或者, 添加就把这个生成的 唯一的缓存更新掉,再用唯一key,拼接查询条件和分页条件,生成新的缓存key, 去维护这个结果集 。这样有个弊端 每次更新或者添加 所有列表里的key 都会失效 所有请求列表的数据 都会访问数据库 这是低效的 增加数据库的压力。 
   二级缓存:维护两个不同的时间缓存时间 ,一个是短期有效 ,一个长期有效  短期有效的维护的是长期有效的id结果集 那么每次从列表里取数据 都是从根据二级缓存里的id 去访问一级缓存的数据。
   这样有两个明显的有点 :1.一级混存的命中提高了  redis 或者memche lru 或者lazy 算法不会那么快的更新掉一级缓存 2.缓存的数据的size 更加合理 之前维护列表数据表达 即使分10条为一页 如果是大型缓存集群 其占用的内存 是非常可怕的 关系型数据存储 这里不做解释 。
 repository层:
namespace App\Repository\Platform;
use App\Models\Platform\HotWordModel;
use App\Cache\Platform\HotWordCache;
use App\Constants\Key\Cache\Platform;

class HotWordRepository
{
protected  $HotWordRepository,$loginUser,$HotWordCache;
// 依赖注入模型 缓存
public function __construct(HotWordModel $HotWordRepository,HotWordCache $HotWordCache)
{
    $this->HotWordRepository = $HotWordRepository;
    $this->HotWordCache      = $HotWordCache;
    $this->loginUser = app('authUser')->getUser();
}
// 获取一条数据
public function BaseGetInfoById($id){
    if($this->HotWordCache::getCache($this->HotWordCache::getFirstCacheKeyById(Platform::PLATFORM_HOT_WORD,__CLASS__,$id))){
               return $this->HotWordCache::getCache($this->HotWordCache::getFirstCacheKeyById(Platform::PLATFORM_HOT_WORD,__CLASS__,$id));
            }else{
                $ret = $this->HotWordRepository->find($id)->toArray();
                 if(is_array($ret)){
                    $this->HotWordCache::setCache($this->HotWordCache::getFirstCacheKeyById(Platform::PLATFORM_HOT_WORD,__CLASS__,$id),$ret);
                    return $ret;
                 }else{
                     return false;
                 }
            }
        }
// 添加一条数据
public function BaseAdd($data){

$data['create_uid'] = $this->loginUser['id'];
$data['create_time']= $data['update_time'] = time();
$res = $this->HotWordRepository->insertGetId($data);
if($res){
    $this->HotWordCache::setCacheSeed(Platform::PLATFORM_HOT_WORD.__CLASS__.md5(serialize($this->HotWordRepository->getTableName())));
    return $res;
}else{
    return false;
}
}
// 修改一条数据
public function BaseEdit($id,$data){

$data['update_uid'] = $this->loginUser['id'];
$data['update_time'] = time();
unset($data['id']);
$res = $this->HotWordRepository->where($this->HotWordRepository->getPrimaryKey(), '=', $id)->update($data);

if($res){
    $this->HotWordCache::setCacheSeed(Platform::PLATFORM_HOT_WORD.__CLASS__.md5(serialize($this->HotWordRepository->getTableName())));
        return $res;
    }else{
        return false;
    }
}
/**
 * @Notes:移除一条数据
 * @User: 张状
 * @param $id  string id
 * @Date: 2019/07/24
 * @return boolean
 */
public function baseRemove($id)
{
    $res = $this->HotWordRepository->where($this->HotWordRepository->getPrimaryKey(), '=', $id)->delete();
    if($res){
        $this->HotWordCache::delCache(Platform::PLATFORM_HOT_WORD.md5(__CLASS__.$id));
        $this->HotWordCache::setCacheSeed(Platform::PLATFORM_HOT_WORD.__CLASS__.md5(serialize($this->HotWordRepository->getTableName())));
        return $res;
    }else{
        return false;
    }
}
/**
 * @Notes:分页获取列表
 * @User: 张状
 * @Date: 2019/07/24
 * @param $fields  string 字段
 * @param $condition  array 筛选条件
 * @param $pageIndex  int   当前页
 * @param $pageSize  int  每页条数
 * @return array
 */
public function baseGetListInfoByCondition(string $fields,array $condition,int $pageIndex = 1,int $pageSize = 20)
{
    $fields = !empty($fields) ? $fields : $this->HotWordRepository->getFillAble();// 获取字段
    $conditions = array();// 字段校验
    if(isset($condition['title'])&&!empty($condition['title'])) $conditions[] = array('title','=',$condition['title']);
    if(isset($condition['sort'])&&!empty($condition['sort']))   $conditions[] = array('sort','=',$condition['sort']);
    if(isset($condition['is_show'])&&!empty($condition['is_show'])) $conditions[] = array('is_show','=',$condition['is_show']);
    if(isset($condition['create_uid'])&&!empty($condition['create_uid'])) $conditions[] = array('create_uid','=',$condition['create_uid']);
    if(isset($condition['create_time'])&&!empty($condition['create_time'])) $conditions[] = array('create_time','>=',strtotime($condition['create_time']));
        if(isset($condition['update_uid'])&&!empty($condition['update_uid'])) $conditions[] = array('update_uid','=',$condition['update_uid']);
        if(isset($condition['update_time'])&&!empty($condition['update_time'])) $conditions[] = array('update_time','=',strtotime($condition['update_time']));
        // 获取缓存key
        $cacheKey  = $this->HotWordCache::getCacheSeed(Platform::PLATFORM_HOT_WORD.__CLASS__.md5(serialize($this->HotWordRepository->getTableName())));
        $cache_key = Platform::PLATFORM_HOT_WORD.'_'.md5(serialize(implode($fields,',').'_'.implode($condition,',').'_'.$pageIndex.'_'.$pageSize).'_'.$cacheKey);

$offset =($pageIndex - 1) * $pageSize;//分页处理
$ret['pageIndex'] = $pageIndex;
$ret['pageSize']  = $pageSize;

if(!empty($this->HotWordCache::getCache($cache_key.'_'.'count'))){
    $ret['total_count'] = $this->HotWordCache::getCache($cache_key.'_'.'count');
}else{
    $ret['total_count'] = $this->HotWordRepository->select($fields)->where($conditions)->count();
}
$ret['total_pages'] = $pageIndex <= 0 ? 1 : intval(ceil($ret['total_count'] / $pageSize));

//从缓存里去数据
if($this->HotWordCache::getSecondCache($cache_key)){
    $ret['rows'] = iterator_to_array($this->getCacheListInfo(explode(',',$this->HotWordCache::getSecondCache($cache_key))));
    return $ret;
}else{
    $ret['rows'] = $this->HotWordRepository->select($fields)->where($conditions)->offset($offset)->limit($pageSize)->get()->toArray();
    if(is_array($ret['rows'])){
        // 入缓存
        $this->HotWordCache::setSecondCache($cache_key,implode(',',array_column($ret['rows'],'id')));
        return $ret;
    }else{
        return array();
    }
   }
}
// 根据缓存
public function getCacheListInfo(array $data){
  foreach ($data as $info){
      yield $this->BaseGetInfoById($info);
   }
}
    service层:

namespace App\Services;
use App\Repository\Platform\HotWordRepository;

class HotWordService
{
protected $HotWordService;
// 依赖注入仓库
public function __construct(HotWordRepository $HotWordService)
{
$this->HotWordService = $HotWordService;
}
// 获取一条热搜词数据
public function getInfoById($id){
return $this->HotWordService->BaseGetInfoById($id);
}
// 获取热搜词列表逻辑
public function getListInfoByCondition(string $fields,array $condition,int $pageIndex = 1,int $pageSize = 20){
return $this->HotWordService->baseGetListInfoByCondition($fields,$condition,$pageIndex,$pageSize);
}
// 添加编辑热搜词逻辑
public function addOneHotWord($data){
if(empty($data['id'])){
   return  $this->HotWordService->BaseAdd($data);
}else{
   return  $this->HotWordService->BaseEdit($data['id'],$data);
}
}
// 删除一条热搜词逻辑
public function deleteHotWordById($id){
return $this->HotWordService->baseRemove($id);
}
     controller层:

namespace App\Http\Controllers\Platform;
use Illuminate\Http\Request;
use App\Services\HotWordService;
use App\Constants\Nomal\RedisList;

class HotWordController extends BaseController
{
private $hotword,$pageIndex,$pageSize,$redisPage;
public function __construct(Request $request,HotWordService $hotword,RedisList $redisPage)
{
    parent::__construct($request);
    $this->hotword = $hotword;
    $this->pageIndex = 1;
    $this->pageSize  = 20;
    $this->redisPage = $redisPage;
}
/**
 * @Notes: 热搜词保存
 * @User: 张状
 * @Date: 2019/7/5
 * @return \Illuminate\Http\JsonResponse
 * @throws PlatformException
 */
public function save()
{
    $this->verification();    //基础验证信息
    $res = $this->hotword->addOneHotWord($this->request->post());
   //if($res)
    //$this->addLog('添加分类', BaseConstant::LOG_ACTION_TYPE_ADD);        //操作日志
    return $this->responseSuccess($res);
}

/**
 * @Notes: 修改热搜词
 * @User: 张状
 * @Date: 2019/7/5
 * @return \Illuminate\Http\JsonResponse
 * @throws PlatformException
 * @throws \Illuminate\Validation\ValidationException
 */
public function edit()
{
    $this->verification();    //验证数据
    $this->validate($this->request, ['id' => 'required|integer|min:1']);     //单独对id验证

    $data = $this->request->post();
    $data['id'] = $this->request->get('id');

    $this->hotword->addOneHotWord($data);
   // $this->addLog('修改分类', BaseConstant::LOG_ACTION_TYPE_EDIT);        //操作日志
    return $this->responseSuccess(true);
}

/**
 * @Notes: 热搜词列表
 * @User: 张状
 * @Date: 2019/7/5
 * @return \Illuminate\Http\JsonResponse
 * @throws \Illuminate\Validation\ValidationException
 */
public function index()
{
    $data = $this->hotword->getListInfoByCondition('',$this->request->all(),
        !empty($this->request->get('pageIndex'))?intval($this->request->get('pageIndex')):$this->pageIndex,
        !empty($this->request->get('pageSize'))?intval($this->request->get('pageSize')):$this->pageSize);
    return $this->responseSuccess($data);
}

/**
 * @Notes: 热搜词详情
 * @User: 张状
 * @Date: 2019/7/5
 * @return \Illuminate\Http\JsonResponse
 * @throws \Illuminate\Validation\ValidationException
 */
public function info()
{
    $this->validate($this->request, ['id' => 'required|integer|min:1']);
    if($this->hotword->getInfoById($this->request->get('id')))  {
        return $this->responseSuccess($this->hotword->getInfoById($this->request->get('id')));
    }else{
        return $this->responseSuccess(false);
    }
}

/**
 * @Notes:删除
 * @User: 张状
 * @Date: 2019/7/5
 * @return \Illuminate\Http\JsonResponse
 * @throws PlatformException
 * @throws \Illuminate\Validation\ValidationException
 */
public function delete()
{
    $this->validate($this->request, ['id' => 'required|integer|min:1']);
    $this->hotword->deleteHotWordById($this->request->get('id'));
    //$this->addLog('修改分类', BaseConstant::LOG_ACTION_TYPE_DELETE);        //操作日志
    return $this->responseSuccess(true);
}

/**
 * 需要验证的字段
 * @return array
 */
public function rules()
{
    return [
        'title'=>'required|unique:platform.hotword|string|max:100',
        'sort'  => 'required|integer|between:0,100000',
        'is_show'  => 'required|integer|between:0,100'
    ];
}
}    
本作品采用《CC 协议》,转载必须注明作者和本文链接
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 1
缓存主要针对于传统的一致性哈希算法某个缓存数据库 ???
redis 高版本支持 redis ???
比一致性哈希算法更为优秀???

看到这里我就放弃了
可不可以加上标点符号啊

4年前 评论
lar_cainiao (楼主) 4年前

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!