Y

ypingcn

这里是 @ypingcn 的个人博客,相信万物逢时而美。期待与你发现更大的世界。

在工作中使用 AI 辅助

AI在职场中的应用广泛且多样,它正在改变各种职业的工作方式和效率。以下是一些主要用途: 自动化和优化常规任务: AI可以自动执行诸如数据输入、文件整理、邮件筛选等重复性任务,从而减少人工操作,提高效率。 智能助手: AI助手如“小佳”可以帮助员工解答问题,提供即时信息和支持,减少查找和解决问题的时间。 招聘和面试: AI可以用于筛选简历、进行初步面试,甚至评估候选人的适合度,如加多宝集团的AI招聘官。 数据分析: 在金融、医疗、市场研究等领域,AI能够处理大量数据,提供深入的分析和预测,帮助企业做出更明智的决策。 客户支持: AI聊天机器人可以提供24/7的客户服务,回答常见问题,处理投诉和建议,提高客户满意度。 创意工作支持: 在设计、写作和艺术领域,AI可以提供创意建议,生成设计草图或文本,帮助创作者加速创作过程。 教育和培训: AI可以个性化教学,提供定制化的学习材料和课程,同时帮助教师评估学生的学习进度和理解程度。 提高生产力和效率: 在制造业中,AI可以用于优化生产线,预测设备故障,减少停机时间,提高生产效率。 医疗诊断和治疗: AI通过分析医疗图像和患者数据,辅助医生进行疾病诊断和治疗方案的制定,提高诊断的准确性和治疗效果。 总之,AI在职场中的应用正在不断扩展,从简单的任务自动化到复杂的决策支持,AI正在成为提高工作效率和质量的重要工具。

2024/11/17
articleCard.readMore

连麦、PK和语音房的实现设计思路

连麦 PK 是秀场直播中的一个重要核心玩法。而语音房同样包含众多的连麦操作。这三类都是不同的角色在自己位置上的行为。 本文意在对已有功能实现的总结和思考,对比中思考改进点 故在此,对这三类场景的数据管理实现上,总结一下相同点与不同点。 存储位置状态数据 首先,PK 可以理解成是连麦上再次叠加的一层玩法,按照这种逻辑,双人 PK 则是需要双人连麦成功后才有的玩法。按照不同的连麦类型可以分成双人连麦和多人连麦,这两种分法,核心的本质在于一场连麦中的人数是2还是N,这个具体数字只需要在发起时确认即可。为统一讨论实现方案,后续仅讨论多人连麦。 其次,语音房是一种包含 8 个人或者 10 个人的位置状态管理,与连麦相比,这个位置信息不需要包含画面相关的信息,取而代之的是说话中的“声纹”。所以连麦和语音房的位置状态数据共同点,至少包含是否有人上座、用户ID的两个基础信息。 再者可以按以下逻辑,抽象出相关的信息存储—— 用户->场次的索引、场次->所有位置信息的数据 那么,第一个实现上的差别就产生了,如何存储所有的位置信息并保证其准确性?以下是两个做法 文档型存储 这里的文档型存储,是类比“文档型数据库”的一种说法。将所有位置数据都抽象到同一个 json 或者其他序列化后的文本中,数据库辅以版本号进行管理。每次位置更新,只需要取出对应的版本号和位置数据,更新位置数据后用乐观锁同步到数据库中,同步失败则有限次地重复之前步骤重试。 连麦会话中的位置信息管理使用了本思路来处理,这样做的好处在于后续可扩展性高,有需要新增的字段数据只需要直接添加,不需要额外的改动。 数据结构设计 位置信息文档: 用户ID (user_id) 场次ID (session_id) 是否有人上座 (is_occupied) 其他相关信息(如声纹信息、时间戳等) 版本号 (version):用于乐观锁同步 场次文档: 场次ID (session_id) 场次名称 (session_name) 开始时间 (start_time) 结束时间 (end_time) 当前版本号 (version):用于记录场次的最新版本 操作流程 创建场次: 在场次文档中插入一条新的记录,并初始化版本号。 用户加入场次: 在位置信息文档中插入一条新的记录,关联用户ID和场次ID,并初始化版本号。 更新位置信息: 读取位置信息文档的当前版本号。 更新位置信息文档中的数据。 使用乐观锁同步更新到数据库中,即比较版本号是否一致。如果不一致,则重试有限次数。 查询位置信息: 直接读取位置信息文档中的数据,并根据需要关联其他相关信息(如用户信息、场次信息等)。 优点 可扩展性高:新增字段数据只需要直接添加到位置信息文档中,不需要额外的改动。 实现简单:相对于关系型存储,文档型存储的实现更为简单直观。 性能较好:对于读多写少的场景,文档型存储的性能表现较好。 缺点 数据一致性:由于使用乐观锁进行同步更新,可能存在一定的数据不一致风险。 查询效率:对于复杂查询和数据分析,文档型存储的效率可能不如关系型存储。 存储空间:由于存储的是序列化后的 JSON 数据,相对于关系型存储可能会占用更多的存储空间。 关系型存储 关系型存储则更侧重于结构化数据的存储和管理。在关系型数据库中,可以将位置信息拆分为多个表,例如用户表、场次表和位置信息表,通过外键关联这些表。每次位置更新,需要执行 SQL 语句来更新相关表中的数据,并通过事务来保证数据的一致性。 关系型存储的优点在于数据结构清晰,查询效率高,适合复杂查询和数据分析。但是,关系型存储的可扩展性相对较差,新增字段可能需要修改表结构。 以下是关系型存储在语音房中的技术细节实现文档: 关系型存储表结构设计 用户表(users) user_id (主键) username email … 场次表(sessions) session_id (主键) session_name start_time end_time … 位置信息表(positions) position_id (主键) user_id (外键,关联用户表) session_id (外键,关联场次表) is_occupied (是否有人上座) voiceprint (声纹信息) … 关系型存储操作流程 创建场次:插入一条新的记录到场次表中。 用户加入场次:插入一条新的记录到位置信息表中,并关联用户表和场次表。 更新位置信息:执行 SQL 语句更新位置信息表中的数据。 查询位置信息:执行 SQL 语句查询位置信息表中的数据,并关联用户表和场次表。 关系型存储优缺点 优点: 数据结构清晰,易于理解和维护。 查询效率高,适合复杂查询和数据分析。 数据一致性好,通过事务保证数据的一致性。 缺点: 可扩展性较差,新增字段可能需要修改表结构。 相对于文档型存储,关系型存储的性能较低。

2023/12/31
articleCard.readMore

数据密集型应用系统设计笔记

Powered by Claude AI 第一章 可靠性、可扩展性和可维护性 这三个指标是设计高质量数据密集型系统的基石 通过冗余机制提高可靠性,避免单点故障 按需扩展更经济高效,逐步水平拆分是常用方法 良好的抽象和松耦合设计提高可维护性 采用合理的缓存策略也很重要 第二章 数据模型和查询语言 关系型数据库的模式、约束以及SQL查询 各种NoSQL数据库数据模型,如键值、文档、列存储等 查询语言的特点、性能考量 不同的数据模型适合不同的场景 第三章 存储与检索 磁盘与内存数据存储结构优化 日志结构、LSM树等写入优化方法 B树等索引结构及查询优化 数据压缩方法 第四章 编码与演化 版本控制、AB测试的重要性 向后兼容的处理 架构演进的模式 服务化与微服务架构优点 第五章 复制 为什么需要复制 同步与异步复制机制 一致性与可用性的平衡 脑裂问题及解决方案 第六章 分区 数据分区的目的和方法 分区键的选择 水平拆分的实现 动态分区调整 第七章 交易 ACID特性及事务实现 两阶段提交协议 PostgreSQL的可串行化 NoSQL数据库的分布式事务 第八章 分布式系统的麻烦 处理延迟的方法 时钟同步与Globally Unique Identifiers 一致性问题与解决方案 幂等性设计的重要性 第九章 批处理 批处理的适用场景 MapReduce、Spark等技术 流处理与微批处理的对比 Lambda架构和Kappa架构 第十章 操作 基础设施管理和自动化 监控与告警系统设计 容量规划方法 数据系统部署和配置最佳实践

2023/9/9
articleCard.readMore

Podcast 是新一代的 Blog?| 播客收听体验分享

闲暇听什么:我的私藏播客清单 我个人是从 2021 年起才开始用AntennaPod接触并收听播客节目的(希望大家能多多支持开源软件~),今年起也会慢慢使用小宇宙APP来收听其他苹果播客上搜不到的节目(AntennaPod主要是使用苹果播客的数据)作为辅助。收听时长也从 2021 年 11.9h 到 2022 年 54.7h 到今年刚过4个月就已经 13.2+33h 了,播客已经慢慢成为我通勤、在家打扫卫生、跑步锻炼等碎片时间里的一个好陪伴了。 Podcast 是新一代的 Blog? 不得不说,播客(Podcast)与博客(Blog)多少有点相似之处。对于创作者而言都是一个分享自己经历和感受的方式,发表自己的意见系统化的将自己的想法留存下来。而读者都可以从文字版的博客、音频版的播客里获取信息,“打破信息差”。音频所带来的“信息密度”远没有文字版的博客信息量大,所以说实话播客并不是一个获取信息最高效的方式,但也因音频声音高低、想象空间的特点,能给听众文字所不具备的陪伴感、临场感和生活气息。 闲暇听什么:我的私藏播客清单 以下几个是我个人比较喜欢的播客节目—— 1.《来都来了》 丸籽和 Nico 两位女生用聊天的形式节目,轻松愉悦的聊天氛围。 2.《起朱楼宴宾客》 创作者是大卫翁,一个金融从业者记录时代和个人经验的分享。 3.《阿弥晚安》 节目最大的特点是播主狂阿弥的声音,以及每一期最后都会用一首歌曲或者轻音乐作为结束。

2023/5/2
articleCard.readMore

2022 年终总结

一言难尽的 2022 年已经完全过去了,这一年对于我而言是相对不顺利的一年,轮流来临的毕业季、人际关系改善有限等都是比较煎熬的事情。无论如何这些事都短暂地结束了,趁有空记录一下这段时间内的变化和收获吧。 图片出自:2022年网易云年度总结 博客篇 博客从 2016 年开始到现在也已经有 6 年了。博客建设上而言,去年单独给博客配置了独立的域名,不再使用其他服务下的二级域名,并做了一点优化,所以目前博客的访问量绝大部分还是来自搜索引擎。 内容建设上来说,2022 年里博客内容更新了 7 篇,比 2021 年的 2 篇更新数量有所提高(之前的更新几乎都是以年为单位的)。除博客内容以外,还更新了几个浏览器资源相关的页面,这部分页面用特定的关键词在搜索引擎搜已经能排前 10 了,也算是意外的收获。 从总结和提高影响力的角度考虑,也尝试写了技术类的文章,后视镜看这几篇技术博客内容的质量还是差强人意的,没有表达出足够的深度,后续还是要针对这块继续改善下。 最后去年也做了一个静态的个人书签的页面,解决了我个人使用不同浏览器的时候快速访问网站的需要(不算是正式的 side project 吧)。 工作篇 去年年行业重锤,我个人虽然也没被刀但也经历过几次大规模毕业季,同事的变动还是比较大的。再加上下半年时不时一次的居家办公,工作上我的心态更多是想跟同事关系上的相处、个人能力和平台资源的关系、工作深度和广度这几点上做改善,这些事多少有了些阶段性的收获,也算是一个积极的信号。 生活篇 生活篇就是除了工作以外的内容。去年加了一个网友群,跟她们的相处让我的性格变得外向了,面对陌生人时也更自然了一点(就这件事我就想说快乐修勾是真的非常讨喜,非常感谢他们)。 去年也对wlb这件事上做了一点其他的尝试,成为了一个小透明的 UP 主(暂时还是「单机版」,视频的互动和观看都很少)。阅读的目标最后并没有实现。side project 还没有任何眉目实现。 认识新朋友这点的进度有限,不过也因为这目标接触了很多人,对很多事情都有了新的理解。昨天偶然因为要办事跟一个同龄的做民宿的陌生人聊了一会,得知对方有去找工作的想法但是很迷茫(这也是我最近的状态),聊了很多民宿相关的事情,还提到了「人与人之间的关系是一种基于交换的契约关系」这点,我还挺赞同契约关系这句的。现在回想起来,我可能还挺享受跟陌生人聊天的,争取今年能有从陌生人/网友到线下朋友的进步出现吧! 投资篇 2022年对于投资而言可以说是一件魔幻的事情(这个词套用到生活上都是一样适用的)。互联网行业公司的爆锤、持续一整年的加息、恒生指数在 10 月份底点位甚至跌到 25 年前的位置、年末短债基金和理财的大幅回撤都对我实现“画线”而言增加了不少的变数。 最后现在看,基本实现了我年初「提高持仓」+「合理配置」的基本目标,亏损也在可以接受的范围内。过去一年偏股混合型指数下跌21.03%,但个人的组合资金加权收益率是-5.86% ,时间加权是-11.57%, 都跑赢了指数。接下来的一年我打算减少对这块的时间投入,考虑用合适的投顾组合减少开黑车。持有的金额也达到了我的预期所以今年不再使用存量资金投入。不预测只应对,悲观预期和乐观收益并不是一件矛盾的事情,所以希望今年能有所收获。 图片出自:2022年万得迎新年启动页配图 最后 希望今年多点平安顺利吧,就不奢望太多了。

2023/1/1
articleCard.readMore

追「个人养老金」热点?不如多了解观望看看

最近有一个很火的话题便是个人养老金了,银行渠道的红包开户大力推广叠加「养老」的焦虑,个人养老金俨然成为最近的热点话题。年轻人对于养老问题都有提前规划的意识和规划(详见《这届年轻人,不到30岁就思考养老的事了》 - 腾讯新闻),那么个人养老金这项制度对于年轻人养老是否有所帮助呢?下面是我最近关于「个人养老金」的观察分析总结,在此与各位分享下。 何为「个人养老金」? 对于这个问题首先引用一段比较正式的文字来解释「个人养老金」项目 个人养老金是指政府政策支持、个人自愿参加、市场化运营、实现养老保险补充功能的制度。个人养老金实行个人账户制,缴费完全由参加人个人承担,自主选择购买符合规定的储蓄存款、理财产品、商业养老保险、公募基金等金融产品,实行完全积累,按照国家有关规定享受税收优惠政策。 很明显「个人养老金帐号」一个能享受国家税收优惠,旨在为养老设计的专用帐号,参与人按需自愿参加、自主选择购买相关产品。 从目前已经公布的细节,「个人养老金」项目有以下几个特点: 目前仅有 36 个先行城市(地区)可以开通账户,可以购买的产品有「储蓄存款、理财产品、商业养老保险、公募基金」四类,与现有基本养老保险相比较是两笔钱,本质上还是一个「个人投资独立账户」,并没有任何保本保收益的承诺。 每个人每年能存入 12000 元,并作为个人所得税专项附加扣除额度(类似于现有的子女教育、住房贷款利息、住房租金和赡养老人等)享受后续税收返还。资金仅能在「达到退休年龄」这种长期的时间后取出来,对应的税率是 3%。所以对于不需要纳税/个人所得对应税率在3%的人,参与这个计划的意义不大。 对于年轻人群体而言选择「公募基金」更为适合,但现有品种只是各种养老目标基金,缺少国债、不同指数基金的选择,品种单一缺少分散风险的有效手段。 后续相关信息的更新,会放在 《财经文章收藏》 个人养老金可买的公募基金相关信息的更新,会放在 《中国公募基金发展大事记》 欢迎访问。 作为养老第三支柱改变个人观念 个人养老金也不是什么新鲜事,早已是养老的三大支柱之一: 第一支柱是政府主导的基本养老保险。截止 2022 年,基本养老保险已覆盖近 10 亿人。 第二支柱是企业年金,主要是经济发达地区的企业自行设立的一个养老补充计划。2021年末,全国有 11.75 万户企业建立企业年金,参加职工 2875 万人。相对于第一支柱而言,能享受到第二支柱的人数是相对小部分。 第三支柱是个人养老金,主要是个人的资金自行投资规划。 作为养老第三支柱的「个人养老金」,更多是一种理念上的改变和制度上的约束。对于我个人而言,税收抵扣+银行开户红包的吸引力有限,流动性差这点也比较劝退。但个人养老这件事上能引导个人从更长期限角度上来规划资金使用,为资金使用明确一个目标和相对期限,做一件长期模糊但又在正确方向上的事情。 如果对自己自控力有信心的人,自己进行投资理财的计划已经是一份「个人养老金」了。不放心自己的人群,选择加入也是个好的选择,「个人养老金」从制度上“逆人性”地限制了资金的取出,投资期限也大幅延长,用税收优惠来慢慢培养长期的习惯。同样遵循「好资产 + 好价格 + 长期持有 = 慢慢增长」的原则来打理自己的金钱,为未来做保障。 本质上而言「个人养老金」是一种现行基本养老保险的补充,对于以下人群参加个人养老金计划的意义不大: 不需要纳税/年度个人所得在 13.6 万享受不到减税优惠的人群。 相信个人投资能力更强并且能真正长期实现的人群。 已有完善的养老计划的人群(如企业年金和商业保险足够用) 因为只能在退休后取出来,所以现在对资金流动性要求较高的人群不适合参加(如 20 多岁刚开始工作需要花钱的地方多时) 养老金替代率 养老金替代率是指退休以后领的养老金占之前工作时收入的比例。这个比例对于维持退休后的生活水平而言至关重要。 举个例子而言,退休前每个月收入是 1 万,退休后领取养老金是 9000,那么替代率就是 90% ,这样子下来对于你后续生活质量而言不会特别大。而且人性而言是由奢入俭是非常困难的,替代率越低带来消极的影响会越大。 现如今已退休一代人的养老金替代率是在国际平均水平以上,甚至还能出现退休工资倒挂先前工作收入的情况。但现在这代年轻人由于统筹账户+人口结构变化等因素,后续年轻人的养老金替代率会明显低于当前,所以越早准备越好,不能单指望退休后靠第一支柱过日子。 构建自己的被动收入 对于年轻人而言,父母养老问题来得更为迫切一点。有一句话说得好,“经济基础决定上层建筑”,物质条件会影响很多事情,所以趁年轻需要提前打好物质基础。 上班是工业革命以来最主流的收入方式,那除去上班以外如何构建自己的“睡后收入”,利用好每个人最宝贵的时间呢,我看到这样的一句话或许能对你有所启发。 全面学习(认知杠杆)* 终身学习(时间杠杆)* 投资理财(财务杠杆) => 被动收入(人生杠杆) 或许「个人养老金」这项能作为一个财务杠杆,在时间的积累下打下更好的物质基础吧。 更多参考资料 《E54 个人养老金账户有必要开吗?太有了|养老那些事儿 02》 《E65 我到底要不要开个人养老金账户?听完这期就懂了|我们和养老的距离 05》 《「个人养老金」来了,我要参与吗?》

2022/12/4
articleCard.readMore

静态页面也能实现短链接 | 个人博客历史(二)

本站点是使用 Jekyll 搭建起来的,所有的页面都是静态页面,在部署的时候页面的就已经确认了,并没有实现服务端的逻辑。但有些链接地址太长,直接分享出去会占用太多字数也不好更新。 例如专题里的《最新版 Firefox 火狐浏览器下载》 《最新版 Librewolf 浏览器下载》 《最新版 Floorp 浏览器下载》 这三个页面,我都增加了「最新版本快速下载」的按钮,原本是直接加上原始下载链接的。但是随着版本更新,每次更新最新版本的地址都需要在三个地方更新三个地址的信息,不仅麻烦,而且会多次触发页面的构建增加不必要的提交历史,于是萌生了实现短链接的逻辑。 实现 实现这个功能主要想好两个问题 “短地址”到“实际地址”的对应关系如何管理 最终地址如何跳转 如何管理 数据管理的话,主要考虑到部署在 Github Page/Gitee Page/COding Page 这类第三方服务上的 Jekyll 都不能调用数据库,而且这个短链功能主要是给博客使用,不会大规模对外,使用数据库来管理就太“重”了。所以可以直接把数据存在一个routes.json文件里,在地址里传进短地址,实际的跳转地址从这个配置文件里获取。 一个例子—— { "floorp-windows-lastest": "https://github.com/Floorp-Projects/Floorp/releases/download/v10.7.0/floorp-win64.installer.exe" } 如何跳转 静态页面跳转的话就没法返回不同的HTTP 301(永久重定向)/302(临时重定向)返回码了。但是在功能体验上来说可以尽可能地让用户的等待时间缩短,且逻辑只能在前端实现。 上网搜索了一下,发现一篇博客给了我启发。可以设置 window.location.href 属性来实现跳转,如果有延时的需要则设置定时器后延迟更新属性。 因为静态页面里获取routes.json时可能会拿到一个带缓存的非最新页面,所以可以在获取的时候加一个?t=[时间戳]的小尾巴,这也是很多网站的常规操作了。 非前端开发者,东拼西凑的代码,贴一下自己的实现。主要逻辑是从地址里的r参数里获取短地址。如 https://example.com/?r=blog 这个地址,blog 就是我们想要的短地址,不存在的短地址则跳转到error.html 上。 function goUrl(config, timeoutMs) { fetch(config + `?t=${Date.now()}`) .then(response => response.json()) .then(routes => { var queries = parseQueryString(location.search.substring(1)) var key = queries['r'] var url = routes[key]; if (!url) url = '/error.html'; console.log(`${key} redirecting to ${url}`); setTimeout(function () { window.location.href = url }, timeoutMs) }) .catch(error => { console.log(error); }); } 最终效果 来看看最终效果吧,逻辑不复杂,虽然并不完美但也是实现了需要的功能。 Firefox 火狐浏览器官网 Floorp 浏览器官网 Librewolf 浏览器官网 参考网页 《不必复杂,静态博客也可以做短链》

2022/11/13
articleCard.readMore

何为同路人? | 人生七年(一)

人生七年,这也是一部纪录片的名字,大致内容讲的是记录不同的小朋友对于例如梦想生活等特定话题的想法,每七年回访他们了解他们的经历和想法的变化。人生七年又七年,现在已经更新到 S7 63 岁了。对于我而言,如果从离开家乡上大学开始算,今年也已经是第七年了。 而我之所以选择这个纪录片的名字作为本次博客的更新标题,除去个人经历的特殊时间点,和想记录自己的想法以外,更多是因为庞博在《脱口秀大会》第五季第六集里关于飞机的话题里有触动到我的一句话。原话是“去上海的还有吗?不过在那个时刻,我真的听到了一个声音说,我要去上海。我仔细听了一下,是十八岁的我自己”。 说来也巧,庞博也曾经是一名写过三行上过太空的代码的程序员。与大众刻板的“格衬衫”沉闷印象不同的是,他在舞台面前更多表现出来的是活跃文本扎实的一面。脱口秀大会对于他而言更像是各种尝试后收获的事业第二曲线,而且在这种转变中认识了很多志同道合的朋友。这些人对于他而言既是舞台上的竞争对手,也是幕后相互帮助的朋友。突破自己原有的圈子,收获的是自己的改变,也收获了同路人。 这个博客是我尝试表达自己的一个地方,也希望借此机会积累下来工作上经验或者其他方面的思考,说来惭愧这部分的内容更新不多后续会争取补上。对于自己的职业,我的理解是这个职业是对于我来说一个完成原始积累的职业,开局不需要太依赖家庭背景,而且与机器打交道的时间长,遇到沟通的事情相对简单的特点避开了我不善表达的缺点。但是随着时间的流逝,我也慢慢地发现了与人聊天的美好,沟通不见得是一件坏事。用当下最流行的MBTI标签来衡量这种变化的话,我的标签从ISFP慢慢地变成了ESFP。学会一点沟通的技巧,工作进度也能有效地推进。而且随着沟通的实践感受和观念的转变,我逐渐发现同事也不仅仅只是工位上的“局域网网友”,而更像是一个有不同优点可以学习和借鉴的榜样,交流中的思维碰撞、细节上的感受和言行举止对我也有明显的改变。 我也已经记不住 7 年前自己说过的话,可能我不是一个太念旧的人,相比过去我更在意现在和憧憬未来。今天是 1024 节日,既然遇上了,那也顺祝自己节日快乐吧~

2022/10/24
articleCard.readMore

简化开发逻辑的用户属性服务设计

一、背景 在实际业务场景中,有很多功能是支持用户自行设置决定开启关闭的。针对每个单独的设置分别编写代码是一种重复的劳动,可以把这部分的逻辑抽象出来减少重复开发。 同样的服务端针对不同用户也会有不同的业务属性,如果每个属性只会有单一的KV逻辑的话,单独的数据操作代码也可以抽象出来提前封装好。 二、接口设计 数据管理上用 UID+Biz 为主键,用户传入的 KEY 为二级主键进行数据管理。 最终的接口设计如下,主要是实现增删改查接口,增和改接口合并为 SET 接口支持同时处理两类。所有接口支持弱条件过滤,SET 接口支持判断旧值符合要求后再更新。 设计对内接口和对外接口,以便对用户权限进行约束管理。 // 划分场景 struct UserAttributeBiz { kTEST = 1, }; // 操作类似 struct UserAttributeOpType { kSTRING = 1, // 简单处理字符串类型(直接覆盖) kNUMBER = 2, // 简单处理数字类型(直接覆盖) kNUMBER_ADD = 3, // 数字类型累加(负数为减) }; // 属性值 struct UserAttribute { 0 optional string sKey; 1 optional string sStrValue; // 字符串类型值,默认为空 2 optional long lNumValue; // 数字类型值,默认为0 3 optional long lTimestampSec; // 最后更新时间,设置时都不填 // 如无必要,客户端调用设置接口时不支持、不设置以下后续字段 4 optional int iOpType = 0; // see alse @UserAttributeOpType 5 optional int iHasOldStrValue = false; // 是否有旧值,SET/DEL 的时候填值将作为条件,GET 暂时用不到 6 optional string sOldStrValue; 7 optional long lExpireTimestamp = 0; // 失效时间(时间戳),设置时都不填 }; // 获取接口 struct GetUserAttributeReq { 0 optional User tUser; 1 optional int iBiz = 0; 2 optional vector<string> vKeys; // 服务端留空则为获取全部。客户端禁止留空 }; struct GetUserAttributeRsp { 0 optional int iCode; 1 optional string sMessage; 2 optional vector<UserAttribute> vItems; }; // 设置接口 struct SetUserAttributeReq { 0 optional User tUser; 1 optional int iBiz = 0; 2 optional vector<UserAttribute> vItems; 3 optional int iCheckKey = 1; // 检查 key 是否符合配置的内容。服务端有效,客户端必须开启。 4 optional int iFetchNew = 0; // 返回更新后的最新值 }; struct SetUserAttributeRsp { 0 optional int iCode; 1 optional string sMessage; 2 optional vector<UserAttribute> vItems; }; // 删除接口 struct DelUserAttributeReq { 0 optional User tUser; 1 optional int iBiz = 0; 2 optional vector<UserAttribute> vItems; // 留空则删除全部 3 optional int iCheckKey = 1; // 检查 key 是否符合配置的内容 }; struct DelUserAttributeRsp { 0 optional int iCode; 1 optional string sMessage; // TODO 返回删除条数?? }; 同样结合之前博客提到的缓存应用,对于一致性要求不高的场景可以再封装一层合并查询+缓存的中间组件,提供给业务进行调用。组件实现的缓存 KEY 可以定义为 UID+BIZ+KEY 三个维度,多个 KEY 则拆分为不同缓存对象。 实际应用上,直播及其相关场景中缓存组件能有 15% 到 60% 不等的命中率,削峰效果良好。 三、优化点 力度更小的限制,目前实现的逻辑只实现了哪些业务 ID 的值对外开放 + 限制 KEY 的取值,没有约束 VALUE 的取值。 支持更多灵活的 KV 取值判断逻辑,例如正则等。 支持配置 KEY 在没有设置的时候返回默认值,不再默认返回空值。 底层数据存储支持按不同的业务 ID 存储到不同的地方,实现底层数据隔离与安全,也方便在不同业务中的快速部署和迁移。

2022/10/14
articleCard.readMore

用户权限服务的设计

背景:在实际业务场景中,或多或少都有定制特殊名单的逻辑,这些名单可以用来作为活动的特邀用户,亦或者是禁止特定条件的访问权限。这些名单简单划分可以分为白名单和黑名单,按实际约束条件则是用户白名单、地区白名单等。这些名单可以是手动由特定人员添加,或者是由任务分析后生成的结果。下面以 Tars 服务来实现上述目标,简化权限判断的业务逻辑。 最后的实现如下,主要逻辑是从不同的数据源加载数据,根据配置中指定的逻辑进行组合,最后提供接口给外部系统调用。 该微服务可以简单设计为名单权限的判断,再在其上增加一个服务来满足实际应用中更为复杂的逻辑需要。 对外接口设计 FunctionID 是用来区分不同的应用场景。 其他字段可以根据需要拓展。 struct AuthResult { 0 optional int iCode; // 判断的结果 1 optional string sMessage; // 错误信息(或者其他需要用作提示的信息) 2 optional int iPermission; // 是否有权限(已经判断了黑名单、白名单的逻辑) 3 optional int iExpireTimestampSec; // 过期时间(秒级),非 0 则为到对应时间后权限可能发生改变。 }; struct AuthReq { 0 optional User tUser; // 用户信息 1 optional vector<long> vFunctionIDs; }; struct AuthRsp { 0 optional int iCode; // 返回值 1 optional string sMessage; // 错误信息(如果有) 2 optional map<long, AuthResult> mResult; // 结果 }; 版本迭代 从头到位,服务的核心逻辑都是从数据库中加载数据到内存中,然后根据配置进行不同的判断返回结果。 v1 直接判断 第一版是直接把所有数据库数据从固定的一个表内按 FunctionID 区分后,加载到内存中,接口在处理请求时就上以 FunctionID 为维度的读锁,然后根据配置从请求体中取出数据,与内存中的数据进行比较。根据名单类型返回 iPermission 字段的值。 v1.1 从固定的表内加载数据判断 随着单个数据源的增加(某种程度上也有名单维护不合理的原因),每次更新内存中的数据都会因为 SQL 语句执行时间过长(网络传输的数据大),导致服务处理其他请求变慢甚至异常。所以针对数据源的加载问题,做了一点细节优化: 增加分页查询的逻辑,默认以数据库自增 ID 为依据,每次数据库查询限制 2000 条(一个拍脑袋定一下的配置),如果需要可以自行配置 Pagination 和 PageSize 调整此处逻辑。 缩小读写数据时的加锁力度,将原有的全局读写单例改为按 FunctionID 划分的不同读写锁。定时更新数据时只对需要更新的部分进行加锁,尽可能避免影响其他数据的判断逻辑。 v1.2 从多变的表内加载数据判断 v1.1 的版本实现里只实现了一个固定语句的查询,形如 select xxx from white_list where function_id = xxx 的语句直接从管理后台中读取手动更新的配置。随着该服务使用范围的推广,已经慢慢无法满足实际需要。 假设有一份名单是由其他部门根据某些条件定时计算出来的,按原有设计只能是每次到处名单再手动加入管理后台里,费时费力。为了更为灵活的实现数据源加载,对数据库读取的逻辑做了以下优化: 支持自定义配置数据库链接,没有设置则直接使用管理后台的数据库链接。 支持配置需要读取的数据库字段,留空则默认为udbuserid。 支持配置需要读取的数据库表名,不再只是原有的 white_list 和 black_list。 增加开关以便关闭默认的function_id = xxx 查询过滤逻辑。 支持配置where 条件的写法。 实现完本版本后,只需要定时重新读取配置即可避免上述人工操作过程,更加方便地支持动态+自动化变更的需要。 v1.3 优化数据的管理逻辑 到目前为止,所有的数据都是存放在一个全局的 map[string][]string mData 中,key 是functionid,value 则是集合,每次请求过来则从一个map[string]int mFunctionID2FieidID 中取出需要请求中的哪个字段,再与 mData 中的集合进行比较。 这种设计无法满足后续对多个字段进行判断的场景,故进行改造。 TODO

2022/7/28
articleCard.readMore