为你的 Laravel 应用添加一个基于 Swoole 的 WebSocket 服务
做了一个基于 Swoole 的 WebSocket 扩展包,可以用来做实时状态推送,或者自定义消息处理实现 im,有需要的可以看看: [giorgio-socket] 使用方法安装安装扩展包 1composer require wu/giorgio-socket 发布配置文件 1php artisan vendor:publish --provider="GiorgioSocket\Providers\SocketServiceProvider" 运行 Socket 服务 1php artisan socket:start 注意事项 可以通过实现 GiorgioSocket\Services\Handlers\Interfaces 下的接口类来自定义自己的业务逻辑。 如果要从服务端发送消息,这里有两种方式: 第一种,借助 Laravel HTTP 客户端 123456Route::get('/socket', function () { \Illuminate\Support\Facades\Http::asForm()->post('http://127.0.0.1:9501', [ 'to' => 2, 'message' => 'server message', ]);}); 第二种:借助 Laravel Listener,需要将 .env 文件中的 QUEUE_CONNECTION 配置修改为 redis 或其他异步队列。配置更改后,运行以下命令:php-artisan queue:work --queue=socket-listener监听队列,然后按以下代码调用 event: 123Route::any('socket', function (Request $request){ \GiorgioSocket\Events\SocketEvent::dispatch($request->get('to'), $request->get('message'));}); 如果你正在使用 laravel/breeze 扩展包,并且使用了 Blade 模板,可以将以下代码粘贴到 dashboard.blade.php 中进行快速测试: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111@auth <div class="py-12"> <div class="max-w-7xl mx-auto sm:px-6 lg:px-8"> <div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg"> <div class="grid grid-cols-1 md:grid-cols-2"> <div class="p-6" id="server-message"> messages:<br/> </div> <div class="p-6"> <label class="block font-medium text-sm text-gray-700 dark:text-gray-300" for="from">from</label> <input class="border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 rounded-md shadow-sm block mt-1 w-full" value="{{ auth()->user()->getKey() }}" id="from"> <label class="block font-medium text-sm text-gray-700 dark:text-gray-300" for="to">to</label> <input class="border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 rounded-md shadow-sm block mt-1 w-full" value="" id="to"> <label class="block font-medium text-sm text-gray-700 dark:text-gray-300" for="message">message</label> <textarea class="border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 rounded-md shadow-sm block mt-1 w-full" id="message"></textarea> <input class="inline-flex items-center px-4 py-2 bg-gray-800 dark:bg-gray-200 border border-transparent rounded-md font-semibold text-xs text-white dark:text-gray-800 uppercase tracking-widest hover:bg-gray-700 dark:hover:bg-white focus:bg-gray-700 dark:focus:bg-white active:bg-gray-900 dark:active:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 dark:focus:ring-offset-gray-800 transition ease-in-out duration-150 mt-3" type="button" id="submit" value="submit"> </div> </div> </div> </div> </div> <script type="text/javascript"> let heartBeatTimer = 0; let socket = connectWebSocket(); function startHeartbeat(interval) { interval = interval || 30; heartBeatTimer = setInterval(function () { sendMessage(null, "heart_beat"); }, interval * 1000); } function stopHeartbeat() { clearInterval(heartBeatTimer); } function connectWebSocket() { const wsServer = 'ws://127.0.0.1:9501'; const socket = new WebSocket(wsServer); let userId = document.getElementById('from').value; socket.onopen = function (evt) { let data = { user_id: userId, type: 'connect' }; console.log('open', data) socket.send(JSON.stringify(data)); }; socket.onmessage = function (evt) { console.log('get message from server: ' + evt.data); if (evt.data !== 'heart_beat') { let data = JSON.parse(evt.data); let message = document.getElementById("server-message") message.innerHTML += data.user_name + ': ' + data.data + '<br/>' } }; socket.onerror = function (evt) { console.log(evt); }; socket.onclose = function () { let data = { user_id: userId, type: 'close' }; socket.send(JSON.stringify(data)); }; return socket; } function sendMessage(to, message) { if (socket != null && socket.readyState === WebSocket.OPEN) { if (message !== 'heart_beat') { let messageBox = document.getElementById("server-message") messageBox.innerHTML += 'me: ' + message + '<br/>' } let from = document.getElementById("from") socket.send(JSON.stringify({ user_id: from.value, user_name: '{{ auth()->user()->name }}', to: to, type: 'message', data: message, })); console.log("webSocket send message:" + JSON.stringify({ user_id: from.value, user_name: '{{ auth()->user()->name }}', to: to, type: 'message', data: message, })); } else { console.log("webSocket closed"); } } let button = document.getElementById("submit"); button.addEventListener('click', function () { let message = document.getElementById("message"); let to = document.getElementById("to"); sendMessage(to.value, message.value) }); </script>@endauth 如有任何疑问,欢迎提交 [issue]
2023年9月流水账
最近沉迷 swift,忙里抽闲做了几个菜单栏小工具,外加一个小工具合集,高产似母猪,啊哈哈哈…… 看看后续能不能再多做几个,到时候搞个开发者账号发到苹果商店去。
php 国密 sm2 sm3 sm4 完整测试类
应用范围及描述 算法类型 国密算法 应用范围及描述 对称加密 SM1 128位数据加密,算法不公开,仅以IP核的形式存在于芯片中。智能IC卡、智能密码钥匙、加密卡、加密机。 非对称加密 SM2 被用来替换RSA算法。常用于身份认证,数据签名,密码交换,256位椭圆曲线。 完整性运算 SM3 256位数据摘要计算,相当于SHA256,数字签名及验证、消息认证码生成及验证、随机数生成 对称加密 SM4 128位数据加密,相当于AES(128) 相关代码php sm2 sm3 sm4 完整测试类,可拖入 laravel unit test 模块运行。基于扩展包 [ lpilp/guomi ] , sm2 与兴业银行有部分区别,sm4 已互通,未做招行验证。 sm2 密钥长度一般为 128 或 130 位,部分使用压缩密钥长度为 66,也就是将密钥分成 x、y,y是偶数就是02,y是奇数就是03,通过 x 可以算出 y。 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290<?phpnamespace Tests\Unit;use FG\ASN1\ASNObject;use FG\ASN1\Exception\ParserException;use Mdanter\Ecc\Crypto\Signature\Signature;use Mdanter\Ecc\Serializer\Signature\DerSignatureSerializer;use PHPUnit\Framework\TestCase;use Rtgm\sm\RtSm2;use Rtgm\sm\RtSm3;use Rtgm\sm\RtSm4;/** * 国密加密测试 * sm4 已与兴业银行调通 */class GmTest extends TestCase{ /** * 获取 sm2 * @return RtSm2 */ private function getSm2(): RtSm2 { return new RtSm2('base64'); } /** * 获取 sm3 * @return RtSm3 */ private function getSm3(): RtSm3 { return new RtSm3(); } /** * 获取 sm4 * @return RtSm4 */ private function getSm4(): RtSm4 { $privateKey = $this->getSm4PrivateKey(); return new RtSm4($privateKey); } /** * 获取 16进制 sm2 密钥 * 生成于工具站 https://www.lzltool.com/SM2 base64格式 * @return string */ private function getSm2PrivateKey(): string { return bin2hex(base64_decode('L8TbMByc+rQmKECWMBjnDQHrXrExqZKdl5S6sBbP07M=')); } /** * 获取 16进制 sm2 公钥 * 生成于工具站 https://www.lzltool.com/SM2 base64格式 * @return string */ private function getSm2PublicKey(): string { return bin2hex(base64_decode('BCxc4cDX1OQEpCD8O7wzPhTOljYg0uzfsMAEanCvYgBIj966+i5pgjwyIOtFSNWLWjoDzLmMJP9nf2cVmiH+aYI=')); } /** * sm2 数据格式化 * @param $dec * @return string */ private function sm2FormatHex($dec): string { $hex = gmp_strval(gmp_init($dec, 10), 16); $len = strlen($hex); if ($len == 64) { return $hex; } return $len < 64 ? str_pad($hex, 64, "0", STR_PAD_LEFT) : substr($hex, $len - 64, 64); } /** * 获取 16位 密钥 * @return bool|string */ private function getSm4PrivateKey(): bool|string { return base64_decode('NmQzZDQ2YTcxMmRjNGE0NQ=='); } /** * 获取待加密字符串 * @return string */ private function getDataStr(): string { return '{"bankCardNo":"6212028190240439021","certNo":"41052619700925136X","userName":"南瓜"}'; } /** * 拼接 sm2 待加密字符串 * @return bool|string */ private function getSm2SignStr(): bool|string { $params = json_decode($this->getDataStr(), true); $signStr = ''; if ($params != null) { ksort($params); foreach ($params as $k => $v) { $signStr .= "{$k}={$v}&"; } } return substr($signStr, 0, strlen($signStr) - 1); } public function test_sm2_sign() { $sm2 = $this->getSm2(); $signStr = $this->getSm2SignStr(); // 加密 $sign = $sm2->doSign($signStr, $this->getSm2PrivateKey()); $encryptStr = base64_decode($sign); try { $a = ASNObject::fromBinary($encryptStr)->getChildren(); } catch (ParserException $e) { $this->fail('加密失败: ' . $e->getMessage()); } $aa = $this->sm2FormatHex($a[0]->getContent()); $bb = $this->sm2FormatHex($a[1]->getContent()); $encryptStr = base64_encode(hex2bin($aa . $bb)); $this->assertNotEmpty($encryptStr); return $encryptStr; } public function test_sm2_verify_sign() { $sm2 = $this->getSm2(); $encryptSignStr = bin2hex(base64_decode($this->test_sm2_sign())); echo 'sm2 sign str: ', $encryptSignStr, PHP_EOL; $r = substr($encryptSignStr, 0, 64); $s = substr($encryptSignStr, 64, 64); $r = gmp_init($r, 16); $s = gmp_init($s, 16); $signature = new Signature($r, $s); $serializer = new DerSignatureSerializer(); $sign = base64_encode($serializer->serialize($signature)); $boolean = $sm2->verifySign($this->getSm2SignStr(), $sign, $this->getSm2PublicKey()) ?? false; echo $boolean ? 'sm2 验签通过' : 'sm2 验签失败', PHP_EOL; $this->assertTrue($boolean); } public function test_sm2_encrypt() { $sm2 = $this->getSm2(); // 压缩公钥 $key = $this->decompressPublicKey('0315edd9126410e9b94b83ee2bcdfeebe9166e84d7aad1b9d16fa923995d28e81f'); $encrypt = $sm2->doEncrypt($this->getDataStr(), $key); $this->assertNotEmpty($encrypt); return $encrypt; } public function test_sm2_decrypt() { $sm2 = $this->getSm2(); $encrypt = $this->test_sm2_encrypt(); $privateKey = 'bf5e3e47e5392a8cdba8e3f854db2d3f5e2c536235303a02898b58d085a8246a'; echo 'sm2 encrypt str: ', $encrypt, PHP_EOL; $decryptStr = $sm2->doDecrypt($encrypt, $privateKey); echo 'sm2 decrypt str: ', $decryptStr, PHP_EOL; $this->assertNotEmpty($decryptStr); $this->assertTrue($decryptStr === $this->getDataStr()); } /** * 获取未压缩公钥 * @param $compressedKey * @return string|null */ function decompressPublicKey($compressedKey): ?string { // 获取压缩标志和X坐标 $flag = substr($compressedKey, 0, 2); $x = substr($compressedKey, 2); // 将16进制字符串转换为大整数 $p = gmp_init('FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF', 16); $a = gmp_init('FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC', 16); $b = gmp_init('28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93', 16); $gx = gmp_init('32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1711D7AFB1B8B4E16', 16); $gy = gmp_init('BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0', 16); $n = gmp_init('FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123', 16); // 计算Y坐标 $x = gmp_init($x, 16); $alpha = gmp_powm($x, 3, $p); $beta = gmp_add(gmp_mod(gmp_mul($a, $x), $p), $b); $y2 = gmp_mod(gmp_add($alpha, $beta), $p); $y = gmp_powm($y2, gmp_div_q(gmp_add($p, 1), 4), $p); if ($flag == "02") { // 如果压缩标志为 02,则Y坐标为偶数 if (gmp_strval(gmp_mod($y, 2)) != "0") { $y = gmp_sub($p, $y); } return "04" . gmp_strval($x, 16) . str_pad(gmp_strval($y, 16), 64, "0", STR_PAD_LEFT); } if ($flag == "03") { // 如果压缩标志为 03,则Y坐标为奇数 if (gmp_strval(gmp_mod($y, 2)) != "1") { $y = gmp_sub($p, $y); } return "04" . gmp_strval($x, 16) . str_pad(gmp_strval($y, 16), 64, "0", STR_PAD_LEFT); } return null; } public function test_sm3() { $sm3 = $this->getSm3(); $signStr = $sm3->digest($this->getDataStr()); echo 'sm3 sign str: ', $signStr, PHP_EOL; $this->assertNotEmpty($signStr); } /** * 测试 byteArr to string * @return string */ public function test_sm4_iv() { $byteArr = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; $iv = call_user_func_array('pack', array_merge(['C*'], $byteArr)); $this->assertTrue(base64_encode($iv) === 'AAAAAAAAAAAAAAAAAAAAAA=='); return base64_encode($iv); } public function test_sm4_encrypt() { try { $iv = $this->test_sm4_iv(); $sm4 = $this->getSm4(); $encryptJsonStr = $sm4->encrypt($this->getDataStr(), 'sm4', base64_decode($iv)); } catch (\Exception $e) { $this->fail('加密异常:' . $e->getMessage()); } $encryptJsonStr = base64_encode(hex2bin($encryptJsonStr)); $this->assertNotEmpty($encryptJsonStr); return $encryptJsonStr; } public function test_sm4_decrypt() { try { $sm4 = $this->getSm4(); $encryptJsonStr = $this->test_sm4_encrypt(); echo "sm4 encrypt str: " . $encryptJsonStr, PHP_EOL; $decryptJsonStr = $sm4->decrypt(bin2hex(base64_decode($encryptJsonStr)), 'sm4', base64_decode($this->test_sm4_iv())); } catch (\Exception $e) { $this->fail('解密异常:' . $e->getMessage()); } echo "sm4 decrypt str: " . $decryptJsonStr, PHP_EOL; echo $decryptJsonStr === $this->getDataStr() ? 'sm4 数据一致' : 'sm4 数据不一致', PHP_EOL; $this->assertNotEmpty($decryptJsonStr); }}
2023 年计划清单
继续减肥 装修&办小红本 下半年软考-系统架构设计师 ,8月中上旬报名,11月下旬考试 我倒要看看这个证我能考几年
向我的小电驴致敬
最近天冷了,骑车上下班又多了些痛苦。 每年的这个时候,我都会出现一些对小电驴命运的担忧,它还能陪我多久呢?即使它才买了两年,但也已经伤痕累累。 截止到今天,我一共丢了两个头盔,都是因为放在车筐然后被人拿走;摔倒三次,都是因为刹车太急导致侧翻;后座的两个脚踏板都有破损,大概率是被停在旁边的车子剐蹭;前轮右侧反光条破碎,大概率同上;左闸轻微磨损,因第二次摔车路面剐蹭;右闸螺丝帽丢失,原因未知;后轮轮毂右侧轻微变形,原因未知;座桶右侧皮套破损,因台风天倒地被旁边车子刮破;后轮轻微漏气,因为我有段时间经常带人…… 每一处损伤都是小电驴的勋章,但也做实了我的不负责任,我是个渣男吧。 我总会想如果哪天我决定要离开上海,那我是该怎么处理它,低价处理掉还是找个物流公司给运回去? 运回去成本有点高,而且连续两年的高强度工作也让我略感担忧:廉颇老矣,尚能饭否?低价卖掉又觉得有点可惜,这家伙明明就性能强悍,充一次电就能驮我跑个 60 公里,如果当初能对它细心点,肯定也不至于这样…… 想来想去又都没有结果。 嗐,贱人就是矫情。
The Fucked 2022
为了应对拖延,2022 年 11 月 25 日,我打开了编辑器,准备提前开写年终总结,然后在当天午休过后,突然接到物业通知让我们赶快回家,因为隔壁公司阳了。 晚上十点,同事说接到电话被判了密接,半个小时后开始转移。十一点二十,住同区的同事说也接到了电话,于是我起床收拾了行李。十分钟后电话响了,通知我等待转移,但是房源紧张需要申请,让我先休息,保持电话畅通。 挂了电话,不安开始涌了上来。躺下,睡不着,起来,又无事可做。 最后只好和衣躺在床上,在公司群里聊天到4点半,期间不停有人上车被拉到方舱, 迷迷糊糊睡到 7 点,被居委电话吵醒,通知我房源还没下来,继续居家,尽量不要和室友接触。 9 点,居委再次来电重申上述信息。 10 点,12 点,均再次接到居委电话重申上述信息。 下午 4 点,接到转运车电话,让我在小区门口等他。那一刻,我惊奇的发现自己放松了下来,遂电话居委告知情况,居委让我跟楼下特勤打声招呼,直接去小区门口等车即可。几分钟后,一辆大巴开到门口,司机穿着防护服,车上其他几个人只是带着口罩,除此以外没有其他防护措施。我心说这跟其他同事不一样啊?看我在车门口犹豫,司机摆手让我上车,然后又开始四处接人。 悬着的心放下之后我是丝毫提不起精神,直接歪脖呼呼睡觉,再次醒来已经晚上 7 点,已经到达方舱门口。 几分钟后,我进入方舱,开始正式的隔离生活。 每天吃饱就睡,睡醒就吃,像猪一样的生活。 怎么说呢,抛开简陋的环境不谈,过的还算挺快。 作为一个普通个体,我着实改变不了什么,也丝毫不想去做些什么。 写下这个过程也只是为了记录一下我平淡生活里为数不多的一丝丝涟漪。 只希望这个狗日的疫情能早点结束,早点恢复正常的生活状态。 peace && love,fuck && crazy.
碎碎念
今天周五,白天躺在床上干活(摸鱼)时掰手指算了一下,今天应该是被封控在家的第 49 天。 未来还要继续封多久?这个我也不知道。可能快解放了吧。 我已经脱下了冬装换上了短袖,从 150 瘦到了 140,被我拎回家的电瓶已经充了第三次电,窗外的景色也从光秃秃变成了郁郁葱葱,枇杷都熟了。今天冒雨去核酸时,还因为看枇杷一脚踩进水坑,淦! 封了这么久,多少是有点麻了,想起来刚开始每天早起收拾的利利索索,楼下一喊就核酸就光速下楼,现在每天能多躺会儿就绝对不会早起,蓬头垢面、邋里邋遢,心情好了可能才洗个头再下去。也没去记核酸了多少次,反正家里做抗原的盒子被我码的整整齐齐,也有一大堆了。本来想等快递通了搞点热熔胶,粘几个小摆件玩,那不是挺有纪念价值嘛。但是……上次拍照发群里被同事喷,说我攒了一堆的大鼻涕,再看总觉得有点恶心🤢。 头发也长了,天天扎个小啾啾,感觉自己还挺好看。要不就留长发吧,解封了去修一修,或者干脆就修也不修,省下一笔又一笔不菲的理发钱,真开心,又可以多喝两罐啤酒。 我迟早会做个扎辫子的程序员,左手键盘,右手鼠标,就像个艺术家。
书单
龙族 一句顶一万句 血腥的盛唐 1942:河南大饥荒 redis 入门指南第二版 深入设计模式 系统架构设计师教程 宫女谈往录 我的前半生 法医宋慈 白鹿原 中国异闻录 万历十五年 叫魂:1768年中国妖术大恐慌 现代管理信息系统 沙海 怒江之战 蜗居 地铁三部曲 中国,少了一味药 被讨厌的勇气 十方异事录 看看能不能把豆瓣书单拉过来…… 难搞,折腾半天现在直接不会发布了😭 已重新配置部署脚本,现切换到豆瓣书单,以后不再更新。
2022 年计划清单
减肥 上半年软考-系统分析师,2月中旬报名,5月下旬考试 下半年软考-系统架构设计师 ,8月中上旬报名,11月下旬考试 没有其他了。 – 2021-12-31 2022-08-06已经瘦了差不多20斤吧,还在继续努力系分的软考g了,因为疫情一直拖,直到昨天看到公告说取消了开始冲刺下半年架构,干巴得💪 2022-11-05多次报名初次参考,可惜论文写的有点惨不忍睹,只能明年再接再厉。
redis 系列 - 1
一. 什么是 redisredis 全称 remote dictionary service,即远程字典服务,是一个基于内存且支持持久化的高性能 key-value 数据库。 redis 优点 读写速度快 支持持久化 支持事务 数据类型丰富 支持主从,读写分离 开源 二. redis 支持的数据类型redis 一共 5 种数据类型: 字符串:string 散列:hash 列表:list 集合:set 有序集合:zset string 字符串string 是最简单的类型,能存储任何形式的字符串,支持字符串,浮点数,整数。 一个字符串类型键允许存储的数据的最大容量为 512 MB。 设置值:set key value 获取值:get key(字符串回复) 删除值:del key 自增:incr key(整数回复) 自减:decr key 按值自增:incrby key value 按值自减:decryby key value 批量设置:mset key val key1 val1 批量获取:mget key key1 使用场景 用户 session 统计计数器 hash 散列hash 可以存储多个键值对之间的映射,可以方便的对同类数据进行归类整合存储。 值的类型同字符串,也可以进行自增操作。 设置值:hset user:1 name wu 获取值:hget user:1 age 按值自增:hincrbr user:1 age 2 获取所有:hgetall user:1 批量获取:hmget user:1 name age 批量设置:hmset user:2 name xu age 18 删除一个:hdel user:1 age 删除全部:del user 字段是否存在:hexists user:1 name 不存在则设值:hsetnx user:1 lock(并发锁) 只取字段名:hkeys key 只取value:hvals key 数据长度:hlen key 使用场景: 购物车 123456hset cart:1001 10091 1 // 用户 1001 添加商品 10091 1件hset cart:1001 10021 2 // 用户 1001 添加商品 10021 2件hincrby cart:1001 10091 1 // 增加商品hlen cart:1001 // 获取商品总数hdel cart:1001 10091 // 删除商品hgetall cart:1001 set 集合字符串的无序集合,每个字符串都是唯一的。可以方便的对数据进行交集、并集、差集等操作。 设置值:sadd key value,values… 获取所有元素:smembers key 移除元素:srem key value,values… 是否在集合:sismember key value 集合差:sdiff key 交集:sinter key 并集:sunion key 数据个数:scard key 随机获取:srandmember key 随机弹出:spop key 使用场景: 好友/关注/粉丝/感兴趣的人集合 随机展示数据 黑白名单 文章标签 123sadd post:1:tags php redis smembers post:1:tags list 列表列表类型可以存储一个有序的字符串列表,常用的操作是向列表两端添加元素,活着获得列表的某一个片段。 列表内部是使用双向链表实现的,所以向列表两端添加元素的时间复杂度为 O(1),这意味着即使是千万数据,获取头部或尾部的 10 条记录和从 20 条数据取出 10 条记录的速度是一样的。 但是通过索引访问元素会比较慢。 添加数据:lpush key value values…,rpush key value values… 取出数据:lpop key,rpop key 获取片段:lrange key start stop,lrange key -2 -1 // -1 表示最右边一个,-2 表示右边第二个 数据长度:llen key 删除前 count 个指定值元素:lrem key count value 获取指定索引:lindex key index 索引赋值:lset key index value 只保留片段:ltrim key start stop 插入元素:linsert key BEFORE|AFTER pivot emelemt 使用场景: 文章存储 队列 新鲜事、日志等很少访问中间元素的应用 zset 有序集合有序集合类型是使用散列表和跳跃表实现的,所以即使读取位于中间部分的数据速度也很快。但是有序集合笔列表类型更耗费内存。 添加数据:zadd fraction 90 wu 获取数据:zscore fraction wu 修改数据:zadd fraction 99 wu 倒序取出:zrange fraction 0 3 正序取出:zrevrange fraction 0 3 取出范围:zrangebyscore fraction 80 100 // 不包括100 (100 增加:zincrby fraction 2 wu 获取元素个数:zcard fraction 指定范围内的元素个数:zcount fraction 80 100 移除元素:zrem fraction wu 按范围移除元素:zremrangebyrank fraction 80 100 使用场景: 排行榜 跳表数据结构 基础命令所有键名:keys *是否存在:exists key获取类型:type key单个删除:del key全部清空:flushall字符长度:strlen key追加 value:append key value 三. 相关知识缓存穿透:是指查询了一个不存在的数据,缓存层不会命中,存储层也不会命中,导致每次查询都要去请求存储层,失去了缓存保护后端存储的意义。解决:存储层不命中以后,仍将空值缓存,但是设置一个较短的过期时间。 缓存击穿:热点 Key,大量并发读请求引起的小雪崩, 就是缓存在某个时间点过期的时候,恰好在这个时间点对这个 Key 有大量的并发请求过来,这些请求发现缓存过期一般都会从后端 DB 加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端 DB 压垮。解决:根据热点 key 设置永不过期;并发锁。 缓存雪崩:缓存设置同一过期时间,引发的大量的读取数据库操作。解决:分散过期时间,设置过期时间时加上一个随机数字;设置永不过期,数据库更新时同步缓存。 是单线程还是多线程首先,不管说 redis 是单线程还是多线程都是不对的,因为要从不同的方面考虑。 5.x 以前是单线程,但是这个单线程并不是说 redis 本身只有一个线程,而是说它在业务处理时是单线程的。 6.x 以后,redis 在业务处理时,读取命令和处理命令分别由不同的线程处理,串行变成了并行,性能得到提升。
