💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
>[info] Redis分布式锁 **为什么需要分布式锁:** ![](https://img.kancloud.cn/ef/d3/efd362fff4781e6ff52589d2571d349b_937x477.png) **保证互斥:** ![](https://img.kancloud.cn/5a/22/5a22ba2813af30484a122adbc26a6967_479x382.png) **死锁:** ![](https://img.kancloud.cn/80/e5/80e54f2332181a2da1fed8409dfe3a54_414x374.png) **加锁:** ~~~php <?php namespace App\Services; use Illuminate\Support\Facades\Redis; class RedisLockService { const LOCK_SUCCESS = 'OK'; const IF_NOT_EXISTS = 'NX'; const MILLISECOND_EXPIRE_TIME = 'EX'; const EXPIRE_TIME = 50; // millisecond => 50s /** * @param $key * 加锁 */ public function lock($key,$uuid,$expire_time = '') { if (empty($expire_time)){ $expire_time = self::EXPIRE_TIME; } $ret = Redis::set($key,$uuid,self::MILLISECOND_EXPIRE_TIME , $expire_time,self::IF_NOT_EXISTS); if ($ret == self::LOCK_SUCCESS){ return true; }else{ return false; } } /** * 释放锁 */ public function unlock($key,$uuid) { $lua =<<<LUA_SCRIPT if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end LUA_SCRIPT; $restful = Redis::eval($lua,1,$key,$uuid); return $restful; } } ?> ~~~ **下单:** ~~~php <?php public function DistributedLock($id) { $RedisLock = new RedisLockService(); $key = "product_".$id; $uuid = Uuid::uuid1($id); $uuid = $uuid->getHex(); $return = ""; try{ if (!$RedisLock->lock($key,$uuid)){ return "当前业务繁忙,请稍后"; }else{ $ret = DB::table("fm_products")->where("id",$id)->first(); if ($ret->store >0){ $ret = DB::table("fm_products")->where("id",$id)->decrement("store"); if ($ret){ $return = "下单成功"; }else{ $return = "下单失败"; } }else{ $return = "库存不足"; } $RedisLock->unlock($key,$uuid); return $return; } }catch (\Exception $exception){ $RedisLock->unlock($key,$uuid); return $exception->getMessage(); } } ?> ~~~ >[info] 内存淘汰策略 **定期删除:** 指的是redis默认是每隔100ms就随机抽取一些设置了过期时间的key,检查其是否过期,如果过期就删除。 **惰性删除:** 获取某个key的时候,redis会检查一下 ,这个key如果设置了过期时间那么是否过期了?如果过期了此时就会删除,不会给你返回任何东西。 **其他淘汰策略:** ~~~ volatile-random:从已设置过期时间的数据集中任意选择数据淘汰。 allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰 volatile-lru:从已设置过期时间的数据集中挑选最近最少使用的数据淘汰。 allkeys-lru:从数据集中挑选最近最少使用的数据淘汰 volatile-ttl:从已设置过期时间的数据集中挑选将要过期的数据淘汰。 no-enviction(驱逐):禁止驱逐数据,这也是默认策略 ~~~ **lru淘汰策略:** 根据冷热数据排序,最热数据排在最上面,反之冷数据反正最底端。 ***** 按照英文的直接原义就是Least Recently Used,最近最久未使用法,它是按照一个非常著名的计算机操作系统基础理论得来的:最近使用的页面数据会在未来一段时期内仍然被使用,已经很久没有使用的页面很有可能在未来较长的一段时间内仍然不会被使用。基于这个思想,会存在一种缓存淘汰机制,每次从内存中找到最久未使用的数据然后置换出来,从而存入新的数据!它的主要衡量指标是使用的时间,附加指标是使用的次数。在计算机中大量使用了这个机制,它的合理性在于优先筛选热点数据,所谓热点数据,就是最近最多使用的数据!因为,利用LRU我们可以解决很多实际开发中的问题,并且很符合业务场景。 ![](https://img.kancloud.cn/c4/ba/c4ba020eaa2cabf7232676d01e80f0d7_760x648.png) ***** **使用php实现lru策略:** ~~~php <?php namespace App\Services; class LruService { /* * 头部节点 */ private $head; /* * 尾部节点 */ private $tail; /* * 最大容量,大于淘汰部位节点指向上一个元素 */ private $capacity; /* * 存储key对应的节点 */ private $hashmap; public function __construct($capacity) { $this->capacity = $capacity; $this->hashmap = array(); $this->head = new LruNodeService(null,null); $this->tail = new LruNodeService(null,null); $this->head->setNext($this->tail); $this->tail->setPrevious($this->head); } /** * 获取元素 * @param $key * @return null */ public function get($key) { if (!isset($this->hashmap[$key])) { return null; } $node = $this->hashmap[$key]; if (count($this->hashmap) == 1) { return $node->getData(); } //先删除已经存在的结点 $this->detach($node); //重新将新结点插入到头结点之后 $this->attach($this->head, $node); return $node->getData(); } /** * 设置key value * @param $key * @param $data * @return bool */ public function put($key, $data) { if ($this->capacity <= 0) { return false; } if (isset($this->hashmap[$key]) && !empty($this->hashmap[$key])) { $node = $this->hashmap[$key]; //重置结点到头结点之后 $this->detach($node); $this->attach($this->head, $node); $node->setData($data); } else { $node = new LruNodeService($key, $data); $this->hashmap[$key] = $node; //添加节点到头部节点之后 $this->attach($this->head, $node); //检测容量是否达到最大值 if (count($this->hashmap) > $this->capacity) { //如果达到最大值 删除尾节点左指针指向的元素 $nodeToRemove = $this->tail->getPrevious(); $this->detach($nodeToRemove); unset($this->hashmap[$nodeToRemove->getKey()]); } } return true; } /** * 删除key * @param $key * @return bool */ public function remove($key) { if (!isset($this->hashmap[$key])) { return false; } $nodeToRemove = $this->hashmap[$key]; $this->detach($nodeToRemove); unset($this->hashmap[$nodeToRemove->getKey()]); return true; } /** * 添加新结点到头结点之后 * @param $head * @param $node */ private function attach($head, $node) { //双向链表插入一个元素到头结点之后 $node->setPrevious($head); $node->setNext($head->getNext()); $node->getNext()->setPrevious($node); $node->getPrevious()->setNext($node); } /** * 删除结点 * @param $node */ private function detach($node) { $node->getPrevious()->setNext($node->getNext()); $node->getNext()->setPrevious($node->getPrevious()); } } ?> ~~~ ~~~php <?php namespace App\Services; class LruNodeService { private $key; //key对应的内容 private $data; //结点右指针 private $next; //结点左指针 private $previous; /** * Node constructor. * @param $key * @param $data */ public function __construct($key, $data) { $this->key = $key; $this->data = $data; } /** * Sets a new value for the node data * @param string the new content of the node */ public function setData($data) { $this->data = $data; } /** * Sets a node as the next node * @param Node $next the next node */ public function setNext($next) { $this->next = $next; } /** * Sets a node as the previous node * @param Node $previous the previous node */ public function setPrevious($previous) { $this->previous = $previous; } /** * Returns the node key * @return string the key of the node */ public function getKey() { return $this->key; } /** * Returns the node data * @return mixed the content of the node */ public function getData() { return $this->data; } /** * Returns the next node * @return Node the next node of the node */ public function getNext() { return $this->next; } /** * Returns the previous node * @return Node the previous node of the node */ public function getPrevious() { return $this->previous; } } ?> ~~~ >[info] 多级缓存 1. 提速 2. 减少服务器压力 3. 提高系统并发 ***** **Openresty安装:** OpenResty由中国 人章亦春发起,提供了很多高质量的第三方模块。OpenResty 是一个强大的 Web 应用服务器,OpenResty可以快速构造出 足以胜任10K 乃至1000K以上并发连接响应的超高性能 Web 应用系统。 360,UPYUN,阿里云,新浪,腾讯网,去哪儿网,酷狗音乐等都是 OpenResty 的深度 用户。 1. 安装依赖 ~~~ yum install readline-devel pcre-devel openssl-devel ~~~ 2. 获取安装包解压 ~~~ wget https://openresty.org/download/ngx_openresty-1.9.7.1.tar.gz # 下载 tar xzvf ngx_openresty-1.9.7.1.tar.gz # 解压 ~~~ 3. 进入安装目录,编译安装 ~~~ cd ngx_openresty-1.9.7.1/ ./configure make ~~~ ***** **使用Openresty-创建lua脚本:** 1. 存 redis ~~~ ngx.header.content_type="application/json;charset=utf8" local cjson = require("cjson") --引入模块 local mysql = require("resty.mysql") --引入mysql模块 local uri_args = ngx.req.get_uri_args() local product_id = uri_args['product_id'] local db = mysql:new() --初始化数据库 db:set_timeout(1000) --设置超时时间 local props = { host = "192.168.29.108", --mysql ip地址 port = 3306, --mysql 端口 database = "starsky", --mysql 数据库 user = "starsky", password = "root" --用户名密码 } local res = db:connect(props) --获得mysql连接 local select_sql = "select id,store from fm_products where id ='"..product_id.."'" --一条查询语句 res = db:query(select_sql) db:close() --执行 local redis = require("resty.redis") --引入redis local red = redis:new() red:set_timeout(2000) --初始化 设置超时时间 local ip ="192.168.29.108" local port = 6379 red:connect(ip,port) red:auth("root") red:set("product_"..product_id,cjson.encode(res)) --存储到redis red:close() ngx.say("{flag:true}") ~~~ 2. 存本地 ~~~ ngx.header.content_type="application/json;charset=utf8" local uri_args = ngx.req.get_uri_args() local product_id = uri_args['product_id'] local cache_ngx = ngx.shared.dis_cache; local adCache = cache_ngx:get('productcache'..product_id); if adCache == "" or adCache == nil then local redis = require("resty.redis") --引入redis local red = redis:new() red:set_timeout(2000) --初始化 设置超时时间 local ip ="192.168.29.108" local port = 6379 red:connect(ip,port) red:auth("root") local rescontent=red:get("product_"..product_id) ngx.say(rescontent) red:close() cache_ngx:set('productcache'..product_id, rescontent, 10*60); else ngx.say(adCache) end ~~~ 3. 配置 nginx >[info] Redis - Key 的设计 1. 内存 2. 可读性 **主键存储查询:** 1. 表作为key的前缀 2. 列名作为key名 3. 主键值保证数据唯一 4. 数据列名 **例如:** user:username:uid:1 starsky product:uid:store 1 ***** **非主键存储查询:** 可以先根据列名设置一个key-value的值,然后在根据查询到的value去查询具体的数据,例如: Set user:username:starsky:uid 1 $uid = Get user:username:starsky:uid $user = get user: $uid:userinfo