温馨提示  2024年 6月我们已经停止内容更新,当前提供存档数据流量服务,谢谢来访。

后端

后端开发知识

后端

保证GO性能的重要方法

GO语言是一种高效、简洁、灵活的编程语言,广泛应用于开发各种类型的应用程序。在GO程序开发中,保证性能是极其重要的,因为它直接关系到应用程序的响应速度、资源占用和稳定性。下面是一些保证GO程序性能的重要方法:1. 优化算法和数据结构:选择正确的算法和数据结构是提高程序性能的基础。在编写代码时,应该根据应用程序的需求和数据特点,选择合适的数据结构和算法,防止使用过于复杂或低效的方案。2. 防止不必要的计算和资源占用:在GO程序中,应该尽可能减少不必要的计算和资源占用,如不必要的网络请求、不必要的磁盘读写等。可以使用缓存技术来存储计算结果或数据副本,以便在需要时快速访问。3. 并发处理任务:GO语言支持并发编程,可以利用goroutine和channel来实现并发处理任务,提高程序的执行效率。但是,应该注意合理地管理并发任务,防止出现竞争条件和死锁等问题。4. 充分利用GO语言的特性:GO语言提供了一些特性和工具,可以帮助开发者提高程序性能。打个比方,可以使用GO模块来管理代码依赖,使用defer和deferpanic来确保资源释放,使用channel来同步并发任务等。5. 优化内存使用:内存使用是影响程序性能的一个重要因素。在GO程序中,应该合理地分配和回收内存,防止出现内存泄漏和过度分配等问题。可以使用垃圾回收机制来自动释放不再使用的内存,也可以手动释放不再使用的引用。6. 代码测试和调试:代码测试和调试是保证程序性能的必要手段。在编写代码后,应该进行充分的测试,确保程序的正确性和性能。在发现问题时,应该及时进行调试和修复,防止问题影响到程序的性能。总的来说保证GO程序性能的重要方法包括优化算法和数据结构、防止不必要的计算和资源占用、充分利用GO语言的特性、优化内存使用、进行代码测试和调试等。通过这些方法,可以提高GO程序的执行效率、降低资源占用、提高稳定性,最终为用户提供更好的使用体验。
2024-03-23 22 次阅读
后端

网站更换域名后,如何使用MySQL批量替换内容

网站开发时,最好不如果要把数据库中的数据和网站域名耦合,但是很多旧的代码早期设计的原因导致这样的问题存在,那么使用mysql批量替换字符串便成为清洗数据常用的方法,今天小编给大家总结几个使用MySQL批量替换内容的方法使用MySQL批量替换内容最简单的方法是UPDATE 表名 SET指定字段 = replace (指定字段, '想要替换的字符串', '替换成的字符串')WHERE 条件;在MySQL数据库中,批量替换内容是一项常用的操作。它可以帮助我们更改大量数据的一致性,同时保持较高的效率。本文将介绍如何使用MySQL批量替换内容,并提供一些技巧和注意事项。步骤一:准备工作 --------在进行批量替换之前,你需要确保MySQL数据库的连接设置正确,并且你已经备份了重要的数据。另外,确保你的MySQL版本是最新的,以便得到最佳的性能和稳定性。步骤二:编写SQL语句 ----------编写SQL语句是进行批量替换的关键步骤。你可以使用UPDATE语句来替换数据表中的内容。下面是一个简单的示例,用于批量替换某张表中的某一列:```sql UPDATE table_name SET column_name = REPLACE(column_name, 'old_value', 'new_value'); ``` 其中,`table_name`是你要更新的表名,`column_name`是要替换的列名,`old_value`是要替换的旧值,`new_value`是替换后的新值。你能够依据需要修改这些参数。步骤三:批量替换多列或多行数据 ---------------要是你需要批量替换多列或多行数据,可以使用循环结构来实现。以下是一个使用MySQL的循环结构进行批量替换的示例:```sql SET @current_value = ''; -- 用于存储当前值UPDATE table_name SET column1 = (CASE WHEN column1 = 'old_value' THEN 'new_value' ELSE column1 END), column2 = (CASE WHEN column2 = 'old_value' THEN 'new_value' ELSE column2 END) WHERE condition; -- 根据需要修改条件 ``` 在上述示例中,你需要根据实际情况修改`table_name`、`column1`、`column2`、`old_value`和`new_value`等参数,并指定适当的条件。这样,你就可以一次更新多列或多行数据。步骤四:性能优化和注意事项 --------------在进行批量替换时,请注意性能优化。尽可能地减少网络传输和磁盘I/O操作,以提高批量替换的效率。确保MySQL服务器的硬件配置能够支持所需的性能需求。除此之外,还要注意以下几点:1. 备份数据:在进行任何数据库操作之前,始终备份你的数据为了不让意外情况发生。 2. 防止长时间运行查询:要是你的批量替换操作需要很长时间才能完成,请考虑把它拆分成多个较小的操作,为了防止长时间占用数据库资源。 3. 监控性能:使用性能监控工具来监视批量替换操作的执行情况,并根据需要调整MySQL服务器的配置或优化查询。 4. 考虑使用索引:在涉及大量数据的列上创建索引可以提高查询性能。在执行批量替换之前,确保相关列上的索引已正确设置。使用MySQL批量替换内容是一项实用的操作,可以帮助你高效地更改大量数据的一致性。通过适当的准备工作、编写SQL语句、优化性能和注意一些关键点,你可以成功地完成批量替换操作,并确保数据的安全性和可靠性。
2023-12-15 23 次阅读
后端

提高Redis性能最简单的10个技巧

Redis是一个快速、可伸缩的内存数据结构存储系统,可以作为后端开发中的关键组件。它提供了各种数据结构,如字符串、哈希表、列表、集合和有序集合等,以及丰富的操作命令,可以用于实现高性能的缓存、数据库、消息队列、会话管理、分布式锁和实时排行榜等功能。以下是Redis在后端开发中的重要性:高性能缓存Redis的读写速度非常快,可以作为后端应用的高性能缓存,提高应用的响应速度,减轻数据库压力。通过将经常访问的数据缓存到Redis中,可以减少数据库的负载,同时提高应用的吞吐量。数据库操作Redis支持各种数据结构的操作,可以作为后端应用的数据库使用,适用于存储轻量级数据。相比较于关系型数据库,Redis的读写速度更快,更灵活,可以更好地满足现代应用的需求。消息队列Redis支持发布/订阅模型,可以作为后端应用的消息队列使用,实现异步处理和解耦。通过Redis实现消息队列,可以更好地处理大量的请求,提高应用的性能和可伸缩性。会话管理Redis可以作为后端应用的会话管理工具,存储用户会话信息,提高会话管理的效率和安全性。相比较于使用Cookie管理会话,使用Redis可以更好地处理大量的会话,减少服务器的负载。分布式锁Redis的原子操作和快速响应特性,可以作为后端应用的分布式锁使用,实现多线程并发控制。通过Redis实现分布式锁,可以更好地控制并发访问,防止资源竞争和数据冲突。实时排行榜Redis的有序集合支持实时的排行榜功能,可以用于社交网络、电商等应用场景。通过Redis实现实时排行榜,可以更好地展示数据,提高用户体验。Redis在后端开发中的重要性无庸赘述,其高性能、丰富的数据结构和功能,使其成为后端开发中缺一不可的工具之一。通过使用Redis,可以更好地实现缓存、数据库、消息队列、会话管理、分布式锁和实时排行榜等功能,提高应用的性能和可伸缩性。接下来我们总结一下提高Redis性能最简单的10个技巧使用合适的数据类型:选择合适的数据类型可以提高读写性能。比如,对于大批量数据的读取操作,使用集合(Set)或有序集合(Sorted Set)可能比使用列表(List)更有效率。优化数据结构:合理设计数据结构可以减少查找和操作的时间复杂度。比如,对于经常进行范围查询的操作,使用有序集合比使用集合更高效。使用缓存:将经常访问的数据缓存到Redis中可以减少数据库负载和响应时间。调整Redis配置:根据实际业务情况调整Redis的配置可以优化性能。比如,适当增加Redis的最大内存限制可以减少数据的频繁落盘,提高读写性能。使用连接池:使用连接池可以复用连接,减少连接的创建和销毁带来的性能损耗。使用Redis Cluster或Redis Sentinel:对于大规模的Redis部署,使用集群或Sentinel可以提供更好的可扩展性和高可用性,最终提高性能和稳定性。减少网络延迟:将Redis和应用部署在同一台机器或同一台服务器上可以减少网络延迟,提高性能。使用合适的数据分片策略:对于Redis Cluster来说,选择合适的数据分片策略可以防止数据热点问题,提高性能。定期清理无用数据:定期清理无用数据可以减少Redis内存的占用,提高性能。使用异步IO:使用异步IO可以提高Redis的I/O操作性能,最终提高整体性能。
2023-12-12 27 次阅读
后端

rust语言适合做什么什么样的软件?

Rust语言适合用于开发操作系统****,网络协议,嵌入式系统等系统级应用,因为它是一种系统级编程语言,提供了高效、安全和并发的编程体验,同时具有防止空指针不同寻常的特性,使编写的程序更加安全和稳定。除此之外,Rust 语言的另一个重要特点是其内存管理效率高,这使得它能够处理大量数据而不会导致系统崩溃。Rust 还具有独特的RAII(资源获取是初始化)机制,当对象超出范围时,会自动调用其析构函数并释放其拥有的资源,最终防止资源泄漏错误。所以呢,Rust 语言适合用于编写需要处理大量数据、对性能要求高、同时又希望得到安全性和稳定性保障的系统级应用。1、防止数据泄露Rust 已经是一种用于生产的成熟技术。作为一种系统编程语言,它允许您保持对低级细节的控制。您可以选择是将数据存储在堆栈上(用于静态内存分配)还是存储在堆上(用于动态内存分配)。在这里,重要的是要提及RAII(资源获取是初始化),这是一个主要与C++相关的代码习语,但也存在于Rust中:每次对象超出范围时,都会调用其析构函数并释放其拥有的资源。您不必手动执行此操作,并且可为了防止资源泄漏错误。2、数据分析高性能和安全性是使 Rust 对开始使用它来执行大量数据分析的科学家如此有吸引力的特性。Rust 的速度非常快,使其成为计算生物学和机器学习的理想选择,在这些应用中,您需要非常快速地处理大量数据。3、游戏开发Rust 是一种面向性能的语言,它可以借助适当的内存管理有效地执行复杂的任务。除此之外,Rust 不使用垃圾回收器,这是最优化的游戏性能的加分项。4、机器学习Rust预计将在机器学习(ML)领域大放异彩,因为它的低级内存控制。该语言使用高级抽象,这些抽象在构建基于 Rust 的神经网络时非常有益。Rust 具有创建现代算法的巨大潜力,但它仍然远不及其他机器学习语言。程序员目前正在尝试 Rust,该语言仍然需要一些时间来成熟到足以创建 ML 算法,宛如我们在 Python 中所做的那样。正在创建新的 Rust 库来开发可靠的神经网络,但这需要一些时间。5、嵌入式设备的开发Rust 是一种低级编程语言,可直接访问硬件和内存,这使其成为嵌入式和裸机开发的绝佳解决方案。您可以使用 Rust 编写操作系统或微控制器应用程序。事实上,有许多用 Rust 编写的操作系统,例如:氧化还原、间充操作系统、QuiltOS、鲁克斯和托克。Mozilla是该语言最初设计的,在其浏览器引擎中使用它。6、网络服务器开发Rust 用极低的资源消耗做到安全高效,且具备很强的大规模并发处理能力,十分适合开发普通或极端的服务器程序,可以用于开发网络服务器。7、编译成 WebAssemblyRust 可以被编译成 WebAssembly,WebAssembly 是一种 JavaScript 的高效替代品。8、直接生成目标可执行程序Rust编译器可以直接生成目标可执行程序,不需要任何解释程序,可用于传统命令行程序。拓展阅读rust语言特点语言语法Rust的具体语法和C,C++类似,都是由花括号限定代码块,还有一样的控制流关键字,例如if,else,while,和for。然而也并非所有的C或者C++关键字被实现了。某些Rust函数(比如关键字match用于模式匹配)对于那些精通这些语言的人就没那么熟悉了。尽管与C/C++极其相似,Rust在深层语法上跟元语言家族的语言像是Haskell更接近。基本上一个函数体的每个部分都是表达式,甚至是控制流操作符。比如,那个普通的if表达式也取代了C的三元表达式。一个函数不需要以return表达式结束,在这样的情况下函数最后的表达式就是返回值。内存安全Rust语言系统设计于保证内存安全,它在安全代码里不允许空指针,悬垂指针和数据竞争。数值只能用一系列固定形式来初始化,要求所有输入已经被初始化。在其它语言中复制函数指针或者有效或者为空,比如在链表和二叉树等数据结构中,Rust核心库提供Option类型,用来测试指针有没有值。Rust同时引入添加语法来管理生命周期,而且编译器通过租借检查器来说明相关理由。内存管理Rust不像Go,Java以及.NET Framework那样使用自动垃圾回收系统。不同的是Rust通过RAII来管理内存和资源,还可选引用计数。Rust以低开销提供资源确定性管理。Rust也支持值的栈分配并不表现暗箱。Rust里也有引用概念(用&符号),不包含运行时引用计数。使得此类指针的安全性已得到租用检查器的编译时验证,阻止悬垂指针和其它形式的未定义行为。所有权Rust有一个所有权系统,所有的值都有一个唯独的属主,值的有效范围跟属主的有效范围一样。值可以借助不可变引用&T传递,可变引用&mut T传递,T值递。在任何时候,或者有多个不可变引用,或者只有一个可变引用。Rust编译器在编译时执行这些规则同时检查所有引用的有效性。类型多态Rust的类型系统支持一种类似类型类的机制,叫“traits”,是被Haskell激发灵感的。这是一种用于特定同质法的设施,通过给类型变量声明添加约束来实现。其它来自Haskell的特性,如更高类型多态还没有支持。
2023-09-21 46 次阅读
后端

常用的linux命令有哪些?

在开发工作中我们常常会用到linux指令(命令行),那么小编今天为大家总结一些经常使用的linux命令行知识一起学习。首先,常用的Linux命令有很多,以下列举一些比较常用的命令,以帮助你更好地了解和使用Linux系统。pwd:显示当前所在的工作目录。在终端中输入“pwd”命令,会输出当前所在目录的绝对路径。ls:列出当前目录下的文件和文件夹。在终端中输入“ls”命令,会列出当前目录下所有的文件和文件夹。使用“ls -l”命令可以以详细列表形式显示文件信息,使用“ls -a”命令可以显示隐藏文件。cd:切换当前工作目录。在终端中输入“cd”命令,后面跟着需要切换到的目录名,就可以切换到该目录。例如,“cd Documents”命令可以将当前目录切换到“Documents”目录。使用“cd ..”命令可以返回上一级目录。mkdir:创建一个新目录。在终端中输入“mkdir”命令,后面跟着需要创建的目录名,就可以创建一个新的目录。例如,“mkdir myfolder”命令可以创建一个名为“myfolder”的新目录。rm:删除指定的文件或目录。在终端中输入“rm”命令,后面跟着需要删除的文件或目录名,就可以将其删除。例如,“rm myfile.txt”命令可以删除当前目录下名为“myfile.txt”的文件。使用“rm -r myfolder”命令可以递归删除整个目录。cp:复制文件或目录。在终端中输入“cp”命令,后面跟着需要复制的文件或目录名,以及目标目录名,就可以将其复制到目标目录。例如,“cp myfile.txt myfolder/”命令可以将当前目录下名为“myfile.txt”的文件复制到“myfolder”目录下。使用“cp -r myfolder newfolder/”命令可以递归复制整个目录。mv:移动文件或目录,也可以用于重命名文件或目录。在终端中输入“mv”命令,后面跟着需要移动或重命名的文件或目录名,以及目标目录名(如果需要重命名),就可以将其移动或重命名到目标目录。例如,“mv myfile.txt newfile.txt”命令可以将当前目录下名为“myfile.txt”的文件移动并重命名为“newfile.txt”。使用“mv myfolder/ ~/Documents/”命令可以将名为“myfolder”的目录移动到“~/Documents/”目录下。sudo:以其他身份来执行命令,预设的身份为root。在Linux系统中,有些命令可能需要管理员权限才能执行,这时就需要使用“sudo”命令。在终端中输入“sudo”命令,后面跟着需要执行的命令,然后输入密码确认身份,就可以以管理员权限执行该命令。例如,“sudo apt-get update”命令可以以管理员权限更新软件包列表。需要注意的是,只有经过授权的用户才能使用“sudo”命令。有哪些linux命令是具备风险的?需要慎用的命令行有哪些? Linux命令有很多,有一些命令如果使用不当可能会导致风险,以下是一些具有风险的Linux命令:rm -rf:这个命令可以快速删除文件夹及其内容,如果使用不当,可能会导致系统崩坏。这个命令甚至可以删除只读文件,并不在乎该文件是否是只读的,而只是在意其父目录是否有写权限。使用这个命令时需要非常小心,因为删除的文件可能无法恢复。如果误删除了根目录下所有文件(例如:rm -rf /),可能会导致系统无法启动。mkfs.ext3 /dev/sda:这个命令会格式化块设备sda,里面的所有数据都会被清除。如果不知道sda里面存有重要数据,执行这个命令后数据将无法恢复。在进行这个操作之前,需要确认sda里面没有重要的数据或者已经备份了重要数据。> file:这个命令常用来清空文件内容或记录命令输出。如果在执行前没有确认输出的文件是空的或者还不存在,原来的文件内容可能无法恢复。以上这些命令都具有风险,使用时需要谨慎,并确保在使用前已经充分了解其作用以及可能带来的后果。如果可以,在使用这些具有风险的命令之前,最好先做一个系统备份,以防出现不可预测的错误。
2023-09-13 24 次阅读
微服务与Serverless一样吗?
后端

微服务与Serverless一样吗?

从单体应用到微服务,我们实现了业务的快速交付。微服务在帮助我们架构解耦的同时,也带来了很多新的挑战,比如运维成本的增加和部署自动化等挑战。即使使用云平台动态管理基础设施,我们仍然要面临如下现实问题:基础设施的创建、配置、维护、安全,比如虚拟机的创建、配置,以及出现安全漏洞后对系统、软件的更新等。随着微服务数量增加,维护的基础设施的规模也对应膨胀,造成创建、配置、维护的困难,并带来安全上的风险。微服务的部署。可能需要定制专门的部署工具去实现零宕机时间部署,或者使用云平台提供的部署服务,但都需要一定的学习、开发和维护成本。服务的伸缩。云平台虽提供了自动伸缩服务,但其策略往往比较简单。比如,设定使用得最少和最多的虚拟机根据CPU使用率去伸缩,但是总的资源不能超过策略设定的最多虚拟机个数。一旦请求超过最大资源能承载的范围,可能会影响用户的使用体验,甚至服务中断。基础设施成本。随着微服务的增加,需要创建越来越多的虚拟机/容器来运行这些微服务,为了保证可用性,这些资源会存在一定的冗余,同时利用率不一定会很高。如果将定时任务也看成一种微服务,它也需要一直运行在虚拟机上,而真正有意义的资源消耗只发生在执行阶段,其余时间的资源都白白浪费了。当微服务数量比较少时,也许看不出明显的成本差异,而当服务数量增加时,可能会导致资源开销的快速增加,造成基础设施的浪费。即便我们将服务部署在容器上,仍然不能避免资源浪费的问题。同时,还需要承担容器集群管理工具,如Kubenetes、Mesos等的维护成本。从资源的使用率来讲,在单体应用下,如果应用的某个功能模块需要水平扩展,那么整个应用都得和它一起水平扩展,这是一种资源的浪费。这样的问题在微服务架构下可能同样存在。假设有一个用户管理的微服务,它的相关API endpoint和每分钟访问数量如下表所示。如果/login的请求剧增,需要扩容,而/register搭了顺风车,但是却没有利用到这些资源,则会造成资源浪费。上线时间(Time to Market)。单体应用的上线时间可能需要半年,打通持续集成流水线的微服务可能只需要两周,然而对于有些领域,需要更快的上线时间。将微服务部署在PaaS上,在一定程度上可以减轻上面的痛楚,但是有没有更完美的方案呢?针对上述的问题,业界提出了Serverless的概念,并且很多的云服务提供商已经提供Serverless服务。1.8.1 什么是ServerlessServerless,顾名思义就是无服务器架构,也就是说从使用者的角度,看不到服务器的存在,只要使用或者直接部署代码即可。它包含两部分内容:BaaS(Backend as a Service后端即服务)。严重或完全依赖第三方应用程序/服务(比如云平台)管理服务器端逻辑和状态的应用程序。比如对于单页面的应用,我们往往会选择将前端的部分部署在AWS S3或者华为云的OBS这样的服务中,前端应用的部署,只是上传静态文件。同时S3或OBS的服务器对我们来说都是不可见的,不用担心任何的维护压力,(大多数情况下)也不用担心如何扩展,由云服务提供商来维护服务的可用性和数据的完整性。FaaS(Function as a Service函数即服务)。开发者实现的服务器端的应用逻辑(微服务甚至粒度更小的服务)以事件驱动的方式运行在无状态的临时的容器中,并且这些容器、计算资源都是由第三方管理。对于BaaS,可以视为云平台提供的托管服务,比如NoSQL数据库,可以用它来减轻微服务关联服务的基础设施维护成本。对于FaaS,从架构层面来看可以视为事件驱动架构,粒度上比微服务更小,小到函数级别。同时尽量做到无状态,服务不再需要复杂的打包等,直接以代码的方式部署,运行时环境由云平台提供。下面我们以AWS Lambda服务为例来解释Serverless的好处以及使用的案例/场景。Serverless的优势Serverless的优势以目前使用较多的AWS的Serverless服务Lambda为例,它提供了如下功能:Java/Nodejs/Python的运行时环境。这意味着可以部署Ruby(JRuby)/Scala/Clojure/Java等运行在JVM上的代码,只是部署时需要编译成class文件打包上传。Nodejs和Python的代码可以直接部署,随时上线。零宕机时间部署。通过Lambda可以很容易地实现函数的蓝绿部署。限量限时的运行资源。Lambda的运行单位是容器,它能使用的资源比较有限,最大分配的内存不超过1.5GB,临时磁盘大小不超过512MB,进程和线程总数不超过1024个等,代码需要的资源超过限制会出错。每个容器的生命周期只有5分钟,超过5分钟会自动销毁,所以一定要保证任务在5分钟内完成。事件驱动。Lambda支持S3、API Gateway、CloudWatch等多种AWS上的服务绑定事件句柄,在事件发生时触发对应的Lambda函数。自动伸缩。每个账户在每个Region上最多能同时运行1000个Lambda函数,算上每个容器的生存周期和并发量,几乎可以认为是无限伸缩了。按请求次数和资源使用量收费。在撰写本文时,Lambda每个月前100万次请求以及400,000 GB秒的运算资源都是免费的。后续的每百万请求约为1.5元人民币,每GB秒的使用费用约为0.00012元人民币。据估算,使用Lambda 部署代码的成本比在EC2上部署服务的成本低30%。从Lambda的特性以及相关的数据,我们很容易看出,相比部署在虚拟机或者容器的微服务,Serverless的好处在于:几乎是“零”维护成本。开发人员可以专注于实现业务逻辑,不用担心基础设施创建、自动化部署的问题,也不用担心服务器维护、升级等问题。降低安全风险。不用管理基础设施,减少了因为维护造成的安全问题,云平台的提供商会替我们保障服务的安全性。更低的资源开销。Lambda通过请求次数和资源使用量收费,而部署在EC2实例上的服务则是按秒收费。更灵活的伸缩。相比部署在虚拟机或者容器上的微服务,需要根据经验或者监控去设置、调整伸缩策略,使用Serverless则几乎不需要考虑这点,它会按需自动伸缩。更快上线。对于开发人员来说,他们只需要直接部署代码到Serverless的服务中,而通常这样的部署很快,几乎是零宕机时间。1.8.2 Serverless的应用场景根据Lambda以及云平台上托管服务的特性,我们可以发现Serverless的应用场景大致如下:事件驱动的业务。比如传统的ETL流程,往往都是通过运行在虚拟机上的Cron任务去轮询或者定时运行处理。但是通过在S3上进行事件绑定,在文件上传时触发处理文件的Lambda函数,然后顺序将事件和对应的处理传递下去。实时业务。比如API,通过API Gateway触发部署在Lambda上的业务逻辑代码,然后返回处理结果。定时任务。不用再像以前一样,为了节省资源将定时任务部署在同一台服务器上。只需将处理的逻辑直接部署在Lambda上,在CloudWatch上设定trigger,定时触发Lambda函数即可。我们以一个宠物商店网站的单体应用为例,它提供了用户管理、购买和搜索的功能,其基本的架构如图1-19所示。单体应用直接部署在虚拟机上,供浏览器访问,相关的数据都保存在数据库中。图1-19 宠物商店的单体应用将宠物商店充分地拆分为微服务后,在AWS上部署的架构如图1-20所示。宠物商店的服务进行前后端分离,同时后端的功能分解为身份验证、购买和搜索的微服务API,每个API有自己对应的数据库。从图中不难看出,为了让服务上线并保证可用性而需要付出的基础设施和维护的成本。图1-20 宠物商店微服务化后部署在AWS上的架构如果使用AWS提供的Serverless的服务,它的架构如图1-21所示。图1-21 宠物商店微服务化后部署在AWS上的Serverless架构将宠物商店应用的前端部署在AWS S3上面,部署可以表现为直接上传前端的静态文件。后端的逻辑拆分到函数级别,分别部署在AWS Lambda上。状态和数据保存在AWS Dynamodb中(Dynamodb是一个全托管的NoSQL数据库)。AWS的API Gateway服务可以作为HTTP代理以及安全入口。其中所用到的服务都是按照使用/请求次数付费,并且可以自动伸缩。部署在S3上的静态页面可以通过CDN缓存来进一步提升性能。一次搜索请求的处理流程如下:1一次搜索请求的处理流程如下:当请求到达API Gateway时,首先返回代理的前端的静态页面。浏览器根据页面中引用的API,发起新的请求,经由API Gateway触发对应的Lambda函数,比如/search请求对应的是Search Function。函数中的代码访问Dynamodb,获取数据并返回搜索结果。上面用到的所有服务都是Serverless的,S3、API Gateway、Dynamodb是BaaS的,Lambda是FaaS的,需要创建、配置的东西非常少,开发人员只需要关注各个业务模块代码的(函数)实现,之后部署代码,就可以完成服务的上线。几乎不用担心伸缩、维护的问题。1.8.3 比较微服务与Serverless当我们比较微服务和Serverless时,实际上比较的是微服务和FaaS。直观上来看,微服务和FaaS的差别在于粒度,而要实现FaaS,首先必须将单体应用演进到微服务,然后才能进一步地分解到函数级别,实现FaaS。我们可以进一步从如下几个方面比较微服务和FaaS。微服务和Serverless不完全是同一层面的事物,但是BaaS可以帮助解决微服务带来的基础设施、维护、资源成本等问题,FaaS进一步改造微服务,降低其实现的粒度,实现更快的上线。Redpoint总结的Serverless LandScape,如图1-22所示。图1-22 Redpoint总结的Serverless LandScape虽然现在FaaS距大规模地被应用还有一定的距离,并且目前它还存在一定的问题,如管理成本、测试、服务治理等,但是Serverless,如Lambda,在AWS已经有一些成功的案例,也有实际的应用场景,如IoT等。一些本地测试、部署的工具也陆续出现,相信这些问题也会被陆续解决。从图1-22来看,Serverless从平台、框架、类库、工具的层面已经形成了一定的规模。目前大部分的云服务供应商都推出了自己的Serverless架构服务,比如AWS的Lambda、Google的Cloud Functions、华为的FunctionStage等。开源的Serverless框架也层出不穷,比如IBM的openwhisk、Oracle的fn等,Serverless的未来值得期待。来都来了,走啥走,留个言呗~ IT大咖说 | 关于版权由“IT大咖说(ID:itdakashuo)”原创的文章,转载时请注明作者、出处及微信公众号。投稿、约稿、转载请加微信:ITDKS10(备注:投稿),茉莉小姐姐会及时与您联系!感谢您对IT大咖说的热心支持!
2023-09-11 25 次阅读 IT大咖说
后端

Redis 可以用作关系数据库是否可行

​让我们从问题“你如何使用Redis?”开始。我相信大多数人将其用作服务的缓存。但是,我希望你知道它不仅仅可以用于缓存。最近,我在一篇文章中看到一份报告,介绍了如何将部分数据迁移到Redis,并将请求首先发送到Redis。现在,我想说的是不是我们如何应用它,而是在使用Spring及其抽象时,我们可能不会立即注意到的替代情况。 让我们尝试编写一个小的Spring应用程序,它将使用两个PostgreSQL和Redis数据库。我想指出的是,我们将在数据库中存储的不是某种扁平对象,而是一个带有嵌套字段(内连接)的关系数据库的完整对象。为此,我们需要安装在Redis中的插件,例如RedisJSON和RediSearch。第一个插件允许我们以JSON格式存储对象,而第二个插件允许我们按照对象的任何字段进行搜索,甚至是嵌套字段。 为了与关系数据库一起工作,我们将选择Spring Data JPA。而为了与Redis一起工作,我们将使用出色的Redis OM Spring库,该库允许在抽象级别上与数据库进行交互。这类似于Data JPA。在底层,Redis OM Spring具有与Spring和Jedis一起使用数据库所需的所有依赖项。先看代码我们先开始编写代码。假设我们需要将某个实体写入数据库。在这个实体中,我添加了其他对象,例如"downtime"、“place”、"reason"等。关系数据库的实体代码如下所示:@Entity @Table(schema = "test", name = "downtime") public class Downtime { ​ @Id private String id; private LocalDateTime beginDate; private LocalDateTime endDate; @ManyToOne(fetch = FetchType.EAGER) @JoinColumn(name = "area") private Place area; @ManyToOne(fetch = FetchType.EAGER) @JoinColumn(name = "cause") private Cause cause; ... 这段代码不需要注释。我们需要为Redis做同样的事情。对象为Redis:@Document public class DowntimeDoc { ​ @Id @Indexed private String id; @Indexed private LocalDateTime beginDate; private LocalDateTime endDate; @Indexed private PlaceDoc area; @Indexed private CauseDoc cause; .... 这种情况下,我们使用@Entity注解而不是@Document注解。这个注解表示我们的对象是一个实体,它将以“包路径+类名+Idx”的键存储在数据库中。@Entity注解表示该字段将被索引以供搜索。如果不指定此注解,那么该字段将保存在数据库中,但搜索它将返回一个空结果。您可以根据需要添加此注解。已经存在于数据库中的数据将异步进行索引;新数据将同步进行索引。接下来,我们将创建一个仓库,它主要用于从数据库中获取数据。关系数据库的示例代码如下所示:public interface DowntimeRepository extends JpaRepository<Downtime, String> { } Redis的例子:public interface DowntimeRedisRepository extends RedisDocumentRepository<DowntimeDoc, String> { } 不同之处在于我们扩展了RedisDocumentRepository接口,该接口扩展了Spring的标准CRUD接口。让我们添加一个方法来查找指定原因的第一个停机时间。public interface DowntimeRepository extends JpaRepository<Downtime, String> { ​ Downtime findFirstByCauseIdOrderByBeginDate(String causeId); } Redis也是如此:public interface DowntimeRedisRepository extends RedisDocumentRepository<DowntimeDoc, String> { ​ DowntimeDoc findTopByCause_IdOrderByBeginDateAsc(String causeId); } 正如你注意到的,如果通过抽象层编写与数据库交互的代码,几乎看不出差异。此外,Redis OM Spring允许使用注解自己编写查询,就像在Spring Data JPA中一样。这是一个HQL查询的示例:@Query("SELECT d FROM Downtime d" + " JOIN FETCH d.area " + " JOIN FETCH d.cause" + " JOIN FETCH d.fixer" + " JOIN FETCH d.area.level " + " WHERE d.area IN ?1 AND (d.beginDate BETWEEN ?2 AND ?3 OR d.cause IN ?4) ") List<Downtime> findAllByParams(List<Place> workPlace, LocalDateTime start, LocalDateTime end, List<Cause> causes); Redis也一样:@Query("(@area_id:{$areas} ) & (@beginDate:[$start $end] | @cause_id:{$causes})") Page<DowntimeDoc> findByParams(@Param("areas") List<String> areas, @Param("start") long start, @Param("end") long end, @Param("causes") List<String> causes, Pageable pageable); 在Redis中,我们只需指定分段的条件。不需要指定需要附加的字段,因为它们总是从数据库中获取的。但是,我们可以通过附加参数来指定我们需要的字段,而不是拉取所有字段。您还可以指定排序、限制和偏移量 - 顺便说一句,HQL中无法指定偏移量。在这个示例中,我传递了Pageable参数给方法,它将在数据库层面上工作,而不是将所有数据拉取到服务中,然后在服务中进行修剪(这与Hibernate的情况相反)。此外,Redis OM Spring还允许您使用EntityStream编写查询,它类似于Stream API。以下是使用EntityStream的上述查询的示例:… entityStream .of(DowntimeDoc.class) .filter(DowntimeDoc$.AREA_ID.in(filter.getWorkPlace().toArray(String[]::new))) .filter(between + " | " + causes) .map(mapper::toEntity) .collect(Collectors.toList()); 在这个示例中,我使用了一个过滤器,使用元模型将参数作为字符串传递给第二个过滤器,以显示这两种选项都是有效的。结果是:EntityStream接受一组中间操作,并在调用终端操作时执行这组操作。Redis OM Spring的细微差别让我讲一些使用Redis OM Spring的细节:无法将UUID用作主键。但是可以将其指定为字符串,并对其进行索引。但是在搜索时,需要转义空格 :@id。{2e5af82m-02af-553b-7961-168878aa521е} 还有一件事:如果通过存储库进行搜索,将无法正常工作,因为代码中有一个表达式将删除所有的转义字符:RedisDocumentRepository。String regex = "(\$" + key + ")(\W+|\*|\+)(.*)"; 因此,为了按这些字段搜索,需要直接在RediSearch中编写查询。我在演示项目中有一个示例,可以演示如何做到这一点。当通过方法搜索时,如果期望返回一个集合,则必须传递一个指示预期行数的参数或在@Query中指定大小;否则,将最多只能收到10条记录。该方法仅支持一个参数进行排序。可以通过编写查询来解决这个问题,使用.FT.SEARCH(@Query)或.FT.AGGREGATE(@Aggregation)方法。上面的列表并不详尽。在使用这些库时,我发现了许多不同的东西,但这只是数据库实现的特定性。还有关于Redis插件的信息和讨论Redis OM Spring的所有功能。结论我展示了目前Redis允许存储具有大量嵌套的对象,并允许通过该对象的字段进行搜索。如果通过存储库中的抽象来处理数据,那么一些人可能看不出与Spring Data JPA的任何区别,特别是如果使用一些简单的查询,如save、delete、findAllBy等,以及通过方法名称进行查询。作者:Artem Artemev
2023-09-04 17 次阅读
后端

Golang 微服框架 Kratos 与它的小伙伴系列 - ORM 框架 - GORM

Golang 微服框架 Kratos 与它的小伙伴系列 - ORM 框架 - GORM什么是 ORM?面向对象编程和关系型数据库,都是目前最流行的技术,但是它们的模型是不一样的。面向对象编程把所有实体看成对象(object),关系型数据库则是采用实体之间的关系(relation)连接数据。很早就有人提出,关系也可以用对象表达,这样的话,就能使用面向对象编程,来操作关系型数据库。简单说,ORM 就是通过实例对象的语法,完成关系型数据库的操作的技术,是"对象-关系映射"(Object/Relational Mapping) 的缩写。ORM 把数据库映射成对象。数据库的表(table) --> 类(class)记录(record,行数据)--> 对象(object)字段(field)--> 对象的属性(attribute)举例来说,下面是一行 SQL 语句。SELECT id, first_name, last_name, phone, birth_date, sexFROM persons WHERE id = 10 复制代码程序直接运行 SQL,操作数据库的写法如下。res = db.execSql(sql);name = res[0]["FIRST_NAME"]; 复制代码改成 ORM 的写法如下。p = Person.get(10);name = p.first_name; 复制代码一比较就可以发现,ORM 使用对象,封装了数据库操作,因此可以不碰 SQL 语言。开发者只使用面向对象编程,与数据对象直接交互,不用关心底层数据库。ORM 有下面这些优点:数据模型都在一个地方定义,更容易更新和维护,也利于重用代码。ORM 有现成的工具,很多功能都可以自动完成,比如数据消毒、预处理、事务等等。它迫使你使用 MVC 架构,ORM 就是天然的 Model,最终使代码更清晰。基于 ORM 的业务代码比较简单,代码量少,语义性好,容易理解。你不必编写性能不佳的 SQL。ORM 也有很突出的缺点:ORM 库不是轻量级工具,需要花很多精力学习和设置。对于复杂的查询,ORM 要么是无法表达,要么是性能不如原生的 SQL。ORM 抽象掉了数据库层,开发者无法了解底层的数据库操作,也无法定制一些特殊的 SQL。什么是 GORM?GORM 是基于 Go 语言实现的 ORM 库,它是 Golang 目前比较热门的数据库 ORM 操作库,对开发者也比较友好,使用非常方便简单。最重要的是,它是一个正经的国产开源库。支持国产!特性全功能 ORM关联 (Has One,Has Many,Belongs To,Many To Many,多态,单表继承)Create,Save,Update,Delete,Find 中钩子方法支持 Preload、Joins 的预加载事务,嵌套事务,Save Point,Rollback To Saved PointContext、预编译模式、DryRun 模式批量插入,FindInBatches,Find/Create with Map,使用 SQL 表达式、Context Valuer 进行 CRUDSQL 构建器,Upsert,数据库锁,Optimizer/Index/Comment Hint,命名参数,子查询复合主键,索引,约束Auto Migration自定义 Logger灵活的可扩展插件 API:Database Resolver(多数据库,读写分离)、Prometheus…每个特性都经过了测试的重重考验开发者友好安装库go get -u gorm.io/gorm 复制代码除此以外,还需要安装数据库的驱动:# 安装SQLite驱动go get -u gorm.io/driver/sqlite # 安装MySQL驱动go get -u gorm.io/driver/mysql # 安装PostgreSQL驱动go get -u gorm.io/driver/postgres # 安装SQL Server驱动go get -u gorm.io/driver/sqlserver # 安装Clickhouse驱动(Clickhouse兼容MySQL的协议,所以直接用MySQL驱动连接也是一样的)go get -u gorm.io/driver/clickhouse 复制代码GORM 的一些数据库基本操作因为数据库是复杂的,SQL 是复杂的,复杂到能够出好几本书,所以是绝不可能在简单的篇幅里面讲完整,只能够将常用的一些操作(连接数据库、CURD)拿来举例讲讲。连接数据库MySQLimport ( "gorm.io/gorm" "gorm.io/driver/mysql") dsn := "user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{}) 复制代码PostgreSQLimport ( "gorm.io/gorm" "gorm.io/driver/postgres") dsn := "host=localhost user=gorm password=gorm dbname=gorm port=9920 sslmode=disable TimeZone=Asia/Shanghai"db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{}) 复制代码SQLiteimport ( "gorm.io/gorm" "gorm.io/driver/sqlite") db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{}) 复制代码SQL Serverimport ( "gorm.io/gorm" "gorm.io/driver/sqlserver") dsn := "sqlserver://gorm:LoremIpsum86@localhost:9930?database=gorm"db, err := gorm.Open(sqlserver.Open(dsn), &gorm.Config{}) 复制代码Clickhouseimport ( "gorm.io/gorm" "gorm.io/driver/clickhouse") dsn := "tcp://localhost:9000?database=gorm&username=gorm&password=gorm&read_timeout=10&write_timeout=20"db, err := gorm.Open(clickhouse.Open(dsn), &gorm.Config{}) 复制代码自动迁移 Automatic Migrationdb.AutoMigrate(&User{}) 复制代码自动迁移功能,会创建表、缺失的外键、约束、列和索引。定义模型type User struct { gorm.Model UserName string NickName string} 复制代码gorm.Model则是包含了通用的一些字段,比如:id、创建时间、更新时间、删除时间等……// gorm.Model definitiontype Model struct { ID uint `gorm:"primaryKey"` CreatedAt time.Time UpdatedAt time.Time DeletedAt gorm.DeletedAt `gorm:"index"`} 复制代码在默认的情况下:表名,将会被转换为 复数形式 以及 蛇形命名法(snake_case),比如:User转换为users。字段名,将被转换为 蛇形命名法(snake_case) 字符串,比如:UserName被转换为user_name。当然,你也可以用column标明字段名的输出:type User struct { gorm.Model UserName string `gorm:"column:username"` NickName string `gorm:"column:nickname"`} 复制代码定义TableName()方法控制表名的输出:func (u User) TableName() string { return "users"} 复制代码增 Createdb.Create(&User{UserName: "TestUserName", NickName: "TestNickName"}) 复制代码删 Delete// 软删除// UPDATE users SET deleted_at="2020-03-13 10:23" WHERE id = user.id;db.Delete(&user, 1) db.Delete(&user) // 批量软删除db.Where("age = ?", 20).Delete(&User{}) // 物理删除// DELETE FROM users WHERE id=10;db.Unscoped().Delete(&user) 复制代码改 Updatedb.Model(&user).Update("nick_name", "NewNickName") // Update - 更新多个字段db.Model(&user).Updates(User{UserName: "NewUserName", NickName: "NewNickName"})db.Model(&user).Updates(map[string]interface{}{"user_name": "NewUserName", "nick_name": "NewNickName"}) 复制代码查 Readvar user User // 获取第一条记录(主键升序)// SELECT * FROM users ORDER BY id LIMIT 1;db.First(&user) // 根据整型主键查找// SELECT * FROM users WHERE id = 10;db.First(&user, 10)db.First(&user, "10") // 根据主键获取记录,如果是非整型主键// SELECT * FROM users WHERE user_name = 'TestUserName';db.First(&user, "user_name = ?", "TestUserName") // SELECT * FROM users WHERE id IN (1,2,3);db.Find(&users, []int{1,2,3}) // 获取一条记录,没有指定排序字段// SELECT * FROM users LIMIT 1;db.Take(&user) // 获取最后一条记录(主键降序)// SELECT * FROM users ORDER BY id DESC LIMIT 1;db.Last(&user) 复制代码与 Kratos 携起手来官方推荐的包结构是这样的:|- data|- biz|- service|- server那么,我们可以把模型定义做成一个 package,放到 data 文件夹下面去:|- data| |- modal|- biz|- service|- server创建数据库客户端在data/data.go文件中添加创建 Gorm 数据库客户端的方法NewGormClient:import ( "gorm.io/driver/clickhouse" "gorm.io/driver/mysql" "gorm.io/driver/postgres" "gorm.io/driver/sqlite" "gorm.io/driver/sqlserver" "gorm.io/gorm") // Data .type Data struct { db *gorm.Client} // NewGormClient 创建数据库客户端func NewGormClient(cfg *conf.Bootstrap, logger log.Logger) *gorm.DB { l := log.NewHelper(log.With(logger, "module", "ent/data/user-service")) var driver gorm.Dialector switch cfg.Data.Database.Driver { default: fallthrough case "mysql": driver = mysql.Open(cfg.Data.Database.Source) break case "postgres": driver = postgres.Open(cfg.Data.Database.Source) break case "clickhouse": driver = clickhouse.Open(cfg.Data.Database.Source) break case "sqlite": driver = sqlite.Open(cfg.Data.Database.Source) break case "sqlserver": driver = sqlserver.Open(cfg.Data.Database.Source) break } client, err := gorm.Open(driver, &gorm.Config{}) if err != nil { l.Fatalf("failed opening connection to db: %v", err) } // 运行数据库迁移工具 if cfg.Data.Database.Migrate { if err := client.AutoMigrate( &models.User{}, ); err != nil { l.Fatalf("failed creating schema resources: %v", err) } } return client} 复制代码并将之注入到ProviderSet// ProviderSet is data providers.var ProviderSet = wire.NewSet( NewGormClient, ...) 复制代码需要说明的是数据库迁移工具,如果数据库中不存在表,迁移工具会创建一个;如果字段存在改变,迁移工具会对字段进行修改。创建 UseCase在 biz 文件夹下创建user.go:package biz type UserRepo interface { ListUser(ctx context.Context, req *pagination.PagingRequest) (*v1.ListUserResponse, error) GetUser(ctx context.Context, req *v1.GetUserRequest) (*v1.User, error) CreateUser(ctx context.Context, req *v1.CreateUserRequest) (*v1.User, error) UpdateUser(ctx context.Context, req *v1.UpdateUserRequest) (*v1.User, error) DeleteUser(ctx context.Context, req *v1.DeleteUserRequest) (bool, error)} type UserUseCase struct { repo UserRepo log *log.Helper} func NewUserUseCase(repo UserRepo, logger log.Logger) *UserUseCase { l := log.NewHelper(log.With(logger, "module", "user/usecase")) return &UserUseCase{repo: repo, log: l}} func (uc *UserUseCase) ListUser(ctx context.Context, req *pagination.PagingRequest) (*v1.ListUserResponse, error) { return uc.repo.ListUser(ctx, req)} func (uc *UserUseCase) GetUser(ctx context.Context, req *v1.GetUserRequest) (*v1.User, error) { return uc.repo.GetUser(ctx, req)} func (uc *UserUseCase) CreateUser(ctx context.Context, req *v1.CreateUserRequest) (*v1.User, error) { return uc.repo.CreateUser(ctx, req)} func (uc *UserUseCase) UpdateUser(ctx context.Context, req *v1.UpdateUserRequest) (*v1.User, error) { return uc.repo.UpdateUser(ctx, req)} func (uc *UserUseCase) DeleteUser(ctx context.Context, req *v1.DeleteUserRequest) (bool, error) { return uc.repo.DeleteUser(ctx, req)} 复制代码注入到biz.ProviderSetpackage biz // ProviderSet is biz providers.var ProviderSet = wire.NewSet( NewUserUseCase, ...) 复制代码创建 Repo在data文件夹下创建user.go文件,实际操作数据库的操作都在此处。package data var _ biz.UserRepo = (*UserRepo)(nil) type UserRepo struct { data *Data log *log.Helper} func NewUserRepo(data *Data, logger log.Logger) biz.UserRepo { l := log.NewHelper(log.With(logger, "module", "User/repo")) return &UserRepo{ data: data, log: l, }} func (r *UserRepo) convertModelToProto(in *models.User) *v1.User { if in == nil { return nil } return &v1.User{ Id: uint32(in.ID), UserName: &in.UserName, NickName: &in.NickName, Password: &in.Password, CreateTime: util.TimeToTimeString(&in.CreatedAt), UpdateTime: util.TimeToTimeString(&in.UpdatedAt), }} func (r *UserRepo) List(_ context.Context, req *pagination.PagingRequest) (*v1.ListUserResponse, error) { var results []models.User result := r.data.db. Limit(int(req.GetPageSize())). Offset(int(req.GetPageSize() * (req.GetPage() - 1))). Find(&results) if result.Error != nil { return nil, result.Error } items := make([]*v1.User, 0, len(results)) for _, res := range results { item := r.convertModelToProto(&res) items = append(items, item) } var count int64 result = r.data.db.Model(&models.User{}). Count(&count) if result.Error != nil { return nil, result.Error } return &v1.ListUserResponse{ Total: int32(count), Items: items, }, nil} func (r *UserRepo) Get(_ context.Context, req *v1.GetUserRequest) (*v1.User, error) { res := &models.User{} r.data.db.First(res, "id = ?", req.GetId()) return r.convertModelToProto(res), nil} func (r *UserRepo) Create(_ context.Context, req *v1.CreateUserRequest) (*v1.User, error) { cryptoPassword, err := crypto.HashPassword(req.User.GetPassword()) if err != nil { return nil, err } res := &models.User{ UserName: req.User.GetUserName(), NickName: req.User.GetNickName(), Password: cryptoPassword, } result := r.data.db.Create(res) if result.Error != nil { return nil, result.Error } return r.convertModelToProto(res), err} func (r *UserRepo) Update(_ context.Context, req *v1.UpdateUserRequest) (*v1.User, error) { var cryptoPassword string var err error if req.User.Password != nil { cryptoPassword, err = crypto.HashPassword(req.User.GetPassword()) if err != nil { return nil, err } } res := &models.User{ UserName: req.User.GetUserName(), NickName: req.User.GetNickName(), Password: cryptoPassword, } result := r.data.db.Model(res).Updates(res) if result.Error != nil { return nil, result.Error } return r.convertModelToProto(res), err} func (r *UserRepo) Delete(_ context.Context, req *v1.DeleteUserRequest) (bool, error) { result := r.data.db.Delete(&models.User{}, req.GetId()) if result.Error != nil { return false, result.Error } return true, nil} 复制代码注入到data.ProviderSetpackage data // ProviderSet is data providers.var ProviderSet = wire.NewSet( NewUserRepo, ...) 复制代码在 Service 中调用package service type UserService struct { v1.UnimplementedUserServiceServer uc *biz.UserUseCase log *log.Helper} func NewUserService(logger log.Logger, uc *biz.UserUseCase) *UserService { l := log.NewHelper(log.With(logger, "module", "service/user")) return &UserService{ log: l, uc: uc, }} // ListUser 获取用户列表func (s *UserService) ListUser(ctx context.Context, req *pagination.PagingRequest) (*v1.ListUserResponse, error) { return s.uc.ListUser(ctx, req)} // GetUser 获取一个用户func (s *UserService) GetUser(ctx context.Context, req *v1.GetUserRequest) (*v1.User, error) { return s.uc.GetUser(ctx, req)} // CreateUser 创建一个用户func (s *UserService) CreateUser(ctx context.Context, req *v1.CreateUserRequest) (*v1.User, error) { return s.uc.CreateUser(ctx, req)} // UpdateUser 更新一个用户func (s *UserService) UpdateUser(ctx context.Context, req *v1.UpdateUserRequest) (*v1.User, error) { return s.uc.UpdateUser(ctx, req)} // DeleteUser 删除一个用户func (s *UserService) DeleteUser(ctx context.Context, req *v1.DeleteUserRequest) (*emptypb.Empty, error) { _, err := s.uc.DeleteUser(ctx, req) if err != nil { return nil, err } return &emptypb.Empty{}, nil} 复制代码注入到service.ProviderSetpackage service // ProviderSet is data providers.var ProviderSet = wire.NewSet( NewUserService, ...) 复制代码将服务注册到 gRPC 服务器当中去:package server // NewGRPCServer new a gRPC server.func NewGRPCServer(cfg *conf.Bootstrap, logger log.Logger, userSvc *service.UserService,) *grpc.Server { srv := bootstrap.CreateGrpcServer(cfg, logging.Server(logger)) userV1.RegisterUserServiceServer(srv, userSvc) return srv} 复制代码这样,我们就有了一个完整的用户服务。
2023-08-17 34 次阅读 喵个咪
后端

CodeIgniter 概览

CodeIgniter 是一个应用程序框架CodeIgniter 是为 PHP 开发人员提供的一套 Web 应用程序工具包。 它的目标是能够让你比从零开始更加快速的完成项目,它提供了一套 丰富的的类库来满足我们日常的任务需求,并且提供了一个简单的 接口和逻辑结构来调用这些库。CodeIgniter 通过最小化你需要的代码量, 让你把更多的精力放到项目的创造性开发上。CodeIgniter 是免费的CodeIgniter 通过 MIT 开源许可协议授权,你可以任意使用。阅读 许可协议 了解更多内容。CodeIgniter 是轻量级的真正的轻量级。我们的核心系统只需要一些非常小的库,这和那些 需要大量资源的框架完全相反。而且,库都是根据请求动态加载, 你需要什么才加载什么,所以核心系统是个非常轻快的系统。CodeIgniter 是快速的真正的快速。我们保证你很难找到一个比 CodeIgniter 性能更好的框架。CodeIgniter 使用 M-V-C 架构CodeIgniter 使用了模型-视图-控制器 架构,它能很好的将逻辑层和表示层分离。 特别是对于那些使用了模板文件的项目来说更好,它能减少模板文件中的代码量。 我们在介绍 MVC 架构的页面有更详细的介绍。CodeIgniter 生成干净的 URLCodeIgniter 生成的 URL 非常干净,而且对搜索引擎友好。不同于标准的 “查询字符串” 方法, CodeIgniter 使用了基于段的方法:example.com/news/article/345 注解默认 URL 中会包含 index.php 文件,但是可以通过更改 .htaccess 文件来去掉它。CodeIgniter 功能强大CodeIgniter 拥有全面的类库,能满足大多数 Web 开发任务的需要, 例如:访问数据库,发送邮件,验证表单数据,会话管理,处理图像, 处理 XML-RPC 数据,等等等等。CodeIgniter 是可扩展的系统可以非常简单的通过你自己的类库和辅助函数来扩展,也可以通过 类扩展或系统钩子来实现。CodeIgniter 不需要模板引擎尽管 CodeIgniter 自带了一个非常简单的可选的模板解析器,但并不强制你 使用模板。模板引擎的性能无法和原生的 PHP 代码相比,另外使用模板引擎 还需要学习一种新的语法,而使用原生 PHP 代码只需要你掌握基本的 PHP 语法即可。看下面的 PHP 代码:<ul> <?php foreach ($addressbook as $name):?><li><?=$name?></li> <?php endforeach; ?> </ul> 再来看看使用模板引擎的代码(伪代码):<ul> {foreach from=$addressbook item="name"}<li>{$name}</li> {/foreach} </ul> 的确,模板引擎中的代码要清晰一点,但是它是以性能为代价的, 因为模板引擎中的伪代码必须要转换回 PHP 代码才能运行。 我们的一个目标是: 性能最大化 ,所以,我们决定不使用模板引擎。CodeIgniter 拥有全面的文档程序员们都喜欢写代码而讨厌写文档,当然,我们也不例外。 但是由于文档和代码是一样重要的,所以我们尽力来做好它。 我们的代码非常简洁并且注释也非常全面。CodeIgniter 拥有一个友好的用户社区你可以看到我们的社区用户在不断的增长,在 社区论坛 里活跃的参与着各种话题。
2023-08-08 39 次阅读