Redis模块的高级使用方式
Redis 模块是Redis的高级功能,允许我们实现特定的自定义数据类型。本质上,模块是一个动态库,可以在启动时或根据命令按需加载到 Redis 中 MODULE LOAD 。模块可以用多种语言编写,包括 C 和 Rust。
我们自己使用 Redis 模块实现新的数据类型是一项艰巨的工作。值得庆幸的是,有许多流行且广泛使用的模块可以解决全文搜索(RediSearch)、时间序列处理(RedisTimeSeries)和原生 JSON 支持(RedisJSON)等问题。让我们概述一下一些使用更广泛的 Redis 模块。
本节提供了 RediSearch、RedisJSON 和 RedisTimeSeries 模块的高级概述,以及与它们相关的重要命令。
要完成本教程的这一部分,您需要 安装最新版本的 Go 和 Docker。所有上述模块都作为Redis Stack的一部分提供 ,它将 Redis 与各种相关的软件包和服务捆绑在一起。您可以使用 Docker 启动 Redis Stack 本地实例 :
docker run -d --name redis-stack -p 6379:6379 redis/redis-stack:latest
在开始之前,让我们创建一个 Go 模块来托管示例:
go mod init go-redis-modules
RedisJSON
在 Redis 中处理 JSON 数据时,传统方法有其局限性。默认情况下,Redis 将值视为简单字符串,缺乏对查询或操作 JSON 等结构化数据的内置支持。因此,开发人员经常将 JSON 存储为 字符串化 值,从而牺牲了对各个字段执行高效操作或利用嵌套结构的能力。这种方法需要解析整个 JSON 文档,即使是微小的更新,导致效率低下并增加处理开销。
RedisJSON 模块通过向 Redis 引入本机 JSON 处理功能来解决这些挑战。它允许您像任何其他本机数据类型一样存储、更新和检索 JSON。
RedisJSON 提供了一组丰富的 JSON 文档操作。以下是一些最常用的命令:
- JSON.SET:在 Redis 中设置 JSON 值。
- JSON.GET:检索与给定键关联的 JSON 值。
- JSON.DEL:从 Redis 中删除 JSON 值。
- JSON.TYPE:返回JSON值的类型,指示它是对象、数组、字符串、数字、布尔值还是null。
- JSON.ARRAPPEND:将一个或多个值附加到 JSON 数组的末尾。
- JSON.ARRLEN:返回 JSON 数组的长度,提供其包含的元素数量。
- JSON.OBJKEYS:检索 JSON 对象的键,返回所有存在键的数组。
- JSON.OBJLEN:返回JSON对象的大小。
- JSON.NUMINCRBY:将 JSON 文档中的数值增加指定的量。
- JSON.STRAPPEND:在 JSON 文档中附加字符串。
下面的示例演示了如何在 Go 应用程序中使用各种 RedisJSON 命令。请注意,我们使用的是 go-redis 客户端。
package main
import (
"context"
"errors"
"fmt"
"log"
"github.com/redis/go-redis/v9"
)
func main() {
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
})
ctx := context.Background()
// JSON.SET
err := rdb.Do(ctx, "JSON.SET", "mydoc", ".", `{"name":"John","credits":30,"cars":["honda","toyota"]}`).Err()
if err != nil {
log.Fatal(err)
}
// JSON.GET
val, err := rdb.Do(ctx, "JSON.GET", "mydoc").Result()
if err != nil {
log.Fatal(err)
}
fmt.Println("JSON.GET result -", val)
// JSON.ARRAPPEND
_, err = rdb.Do(ctx, "JSON.ARRAPPEND", "mydoc", ".cars", `"audi"`).Int()
if err != nil {
log.Fatal(err)
}
fmt.Println("added audi to list of cars using JSON.ARRAPPEND")
// JSON.GET
val, err = rdb.Do(ctx, "JSON.GET", "mydoc").Result()
if err != nil {
log.Fatal(err)
}
fmt.Println("lastest document", val)
// JSON.NUMINCRBY
_, err = rdb.Do(ctx, "JSON.NUMINCRBY", "mydoc", ".credits", 5).Result()
if err != nil {
log.Fatal(err)
}
fmt.Println("incremented credits by 5 using JSON.NUMINCRBY")
// JSON.STRAPPEND
_, err = rdb.Do(ctx, "JSON.STRAPPEND", "mydoc", ".name", `", Jr."`).Int()
if err != nil {
log.Fatal(err)
}
fmt.Println("updated name using JSON.STRAPPEND")
// JSON.GET
val, err = rdb.Do(ctx, "JSON.GET", "mydoc").Result()
if err != nil {
log.Fatal(err)
}
fmt.Println("latest document", val)
// JSON.DEL
_, err = rdb.Do(ctx, "JSON.DEL", "mydoc").Int()
if err != nil {
log.Fatal(err)
}
fmt.Println("deleted document using JSON.DEL")
err = rdb.Do(ctx, "JSON.GET", "mydoc").Err()
if errors.Is(err, redis.Nil) {
fmt.Println("document 'mydoc' could not be found")
}
}
要运行上述代码,请将其复制到名为的文件中 main.go 并执行以下命令:
# get dependencies
go get github.com/redis/go-redis/v9
# run program
go run main.go
请注意,我们不需要获取整个 JSON 文档来更新它。RedisJSON 提供精细操作来处理 JSON 数据,从而提高应用程序性能(减少延迟)并降低成本(减少网络带宽)。
RediSearch
RediSearch模块通过强大的全文搜索功能增强了Redis。它引入了基于倒排索引的搜索引擎,允许高效且可扩展的文本搜索。使用 RediSearch,您可以索引文本数据、执行复杂的搜索查询并检索相关搜索结果。
它提供关键字搜索、精确短语匹配、布尔运算、分页、排序和过滤等功能。
RediSearch 根据搜索结果与查询的匹配程度对搜索结果进行评分,使您能够对搜索结果进行排名和确定优先级。
以下是一些最常用的 RediSearch 命令:
- FT.CREATE:使用指定的架构和配置选项创建新的搜索索引。
- FT.SEARCH:对指定索引执行搜索查询,根据给定的搜索条件返回匹配的文档。
- FT.AGGREGATE:对指定索引进行聚合查询,根据给定的聚合条件返回聚合结果。
- FT.INFO:检索有关搜索索引的信息,包括统计信息、配置设置和架构详细信息。
- FT.DROPINDEX:删除搜索索引,删除所有文档和关联数据。
下面的示例演示了如何在 Go 应用程序中使用各种 RediSearch 命令。请注意redisearch-go客户端的用法 。
package main
import (
"fmt"
"math/rand"
"strconv"
"github.com/RediSearch/redisearch-go/redisearch"
"github.com/gomodule/redigo/redis"
)
var pool *redis.Pool
var client *redisearch.Client
var cities = []string{"new york", "london", "paris"}
func init() {
pool = &redis.Pool{Dial: func() (redis.Conn, error) {
return redis.Dial("tcp", "localhost:6379")
}}
client = redisearch.NewClientFromPool(pool, "user-index")
}
func main() {
schema := redisearch.NewSchema(redisearch.DefaultOptions).
AddField(redisearch.NewTextFieldOptions("name", redisearch.TextFieldOptions{})).
AddField(redisearch.NewTextFieldOptions("city", redisearch.TextFieldOptions{}))
indexDefinition := redisearch.NewIndexDefinition().AddPrefix("user:")
client.CreateIndexWithIndexDefinition(schema, indexDefinition)
fmt.Println("redisearch index created")
conn := pool.Get()
for i := 1; i <= 10; i++ {
hashName := "user:" + strconv.Itoa(i)
val := redis.Args{hashName}.AddFlat(map[string]string{"user_id": strconv.Itoa(i), "city": cities[rand.Intn(len(cities))]})
conn.Do("HSET", val...)
fmt.Println("created hash -", hashName)
}
docs, total, _ := client.Search(redisearch.NewQuery("*"))
fmt.Println("no. of indexed documents =", total)
docs, total, _ = client.Search(redisearch.NewQuery("@city:(paris|london)"))
fmt.Println("found", total, "users in london or paris")
for _, doc := range docs {
fmt.Println("document ID -", doc.Id)
fmt.Println("document attributes -", doc.Properties)
}
err := client.DeleteDocument("user:1")
if err != nil {
fmt.Println("failed to delete document (hash) user:1", err)
}
fmt.Println("deleted document (hash) user:1")
_, total, _ = client.Search(redisearch.NewQuery("*"))
fmt.Println("no. of indexed documents =", total)
err = client.DropIndex(true)
if err != nil {
fmt.Println("failed to drop index", err)
}
fmt.Println("dropped index and documents")
_, total, _ = client.Search(redisearch.NewQuery("*"))
fmt.Println("no. of indexed documents =", total)
}
要运行上述代码,请将其复制到名为的文件中 main.go 并执行以下命令:
go get github.com/RediSearch/redisearch-go/redisearch
go get github.com/gomodule/redigo/redis
go run main.go
我们使用 Hash 作为文档,但 RediSearch 也可以使用 RedisJSON。当添加或删除文档时,索引会自动更新。本示例演示了一些简单的操作,包括获取所有文档,以及通过城市属性上的 ORclause 进行过滤,以获取居住在 "伦敦 "或 "巴黎 "的用户。
Redis时间序列
在 Redis 中处理大量时间序列数据时,有一些常用的技术。这些涉及将数据存储在 Redis 排序集中,其中分数表示时间戳,成员表示数据值。另一种方法是使用带有时间戳的字符串键作为键名称的一部分。每个键值对代表时间序列中的一个特定数据点。
然而,这些技术在处理大量时间序列数据时存在一些局限性。传统的 Redis 数据结构(如字符串键或排序集)的查询能力有限,并且难以执行复杂的聚合。此外,在处理大型数据集时,将每个数据点存储为单独的键值对可能会消耗大量内存,这通常是时间序列的情况。
RedisTimeSeries模块是原生的时序数据结构,为时序数据提供高效的存储和查询能力。它添加了查询功能,例如检索某个时间范围内的数据、执行聚合(例如求和、计数、平均值)、下采样和插值。它还允许我们定义自动过期或缩减数据采样的保留策略,从而实现高效的数据保留并降低存储要求。RedisTimeSeries 还与生态系统中的其他工具集成,例如 Prometheus 和 Grafana。
RedisTimeSeries 提供了丰富的命令集。以下是一些最常用的:
- TS.CREATE:使用指定的键、标签和保留策略创建新的时间序列。
- TS.ADD:将新数据点添加到时间序列,并将其与时间戳和值相关联。
- TS.RANGE:从指定时间范围内的时间序列中检索一系列数据点。
- TS.MRANGE:检索指定时间范围内多个时间序列的数据点。
- TS.GET:从时间序列中检索最新的数据点。
- TS.MGET:从多个时间序列中检索最新的数据点。
- TS.INCRBY:在特定时间戳处增加时间序列中数据点的值。
- TS.DECRBY:在特定时间戳处减少时间序列中数据点的值。
- TS.INFO:检索有关时间序列的元数据信息,例如样本数量、内存使用情况和保留策略。
- TS.QUERYINDEX:对时间序列执行基于索引的查询,根据指定的过滤器和聚合检索数据点。
下面的示例演示了如何在 Go 应用程序中使用各种 RedisTimeSeries 命令。请注意,我们使用的是 redistimeseries-go 客户端。
package main
import (
"fmt"
"log"
"math/rand"
"time"
redistimeseries "github.com/RedisTimeSeries/redistimeseries-go"
)
const tsName = "temperature:living_room"
func main() {
client := redistimeseries.NewClient("localhost:6379", "", nil)
// TS.CREATE
err := client.CreateKeyWithOptions(tsName, redistimeseries.DefaultCreateOptions)
if err != nil {
log.Fatal(err)
}
fmt.Println("created time series key", tsName)
tempOptions := []float64{24, 24.5, 25}
var storedTS []int64
for i := 1; i <= 10; i++ {
// TS.ADD
res, err := client.AddAutoTs(tsName, tempOptions[(rand.Intn(len(tempOptions)))])
storedTS = append(storedTS, res)
if err != nil {
log.Fatal(err)
}
time.Sleep(1 * time.Millisecond)
}
fmt.Println("added time series data")
// TS.GET
lastDataPoint, err := client.Get(tsName)
if err != nil {
log.Fatal(err)
}
fmt.Printf("lastest data point - timestamp: %v, temp: %v\n", lastDataPoint.Timestamp, lastDataPoint.Value)
// TS.RANGE
dataPoints, err := client.RangeWithOptions(tsName, storedTS[0], storedTS[len(storedTS)-1], redistimeseries.DefaultRangeOptions)
if err != nil {
log.Fatal(err)
}
fmt.Println("TS.RANGE result")
for _, dp := range dataPoints {
fmt.Printf("timestamp: %v, temp: %v\n", dp.Timestamp, dp.Value)
}
}
要运行上述代码,请将其复制到名为的文件中 main.go 并执行以下命令:
go get github.com/RedisTimeSeries/redistimeseries-go
go run main.go
Redis 模块:最佳实践
本节介绍了一些在这些模块的实际部署中优化可扩展性和性能的最佳实践。
RedisJSON
- 优化 JSON 结构以实现高效访问:设计 JSON 结构时,请考虑应用程序的访问模式。尽可能展平 JSON 层次结构以避免深层嵌套。这使您可以有效地访问特定字段,而无需获取整个 JSON 文档。使用 RedisJSON 的路径表达式来检索或修改特定的 JSON 元素,而无需解析整个文档。
- 考虑内存管理和数据大小:RedisJSON 将 JSON 文档存储为 Redis 字符串。请记住,Redis 的最大字符串大小限制为 512 MB。如果您的 JSON 文档很大,请考虑将它们分成更小的部分或使用压缩。
- 您可以使用 RedisJSON 作为 RedisSearch 的索引:此集成使您能够高效地执行复杂的搜索操作并根据搜索条件检索 JSON 文档。
Redis搜索
- 仔细选择和优化搜索架构:设计搜索架构时,请考虑应用程序的特定搜索要求。定义需要可搜索的字段并选择适当的字段类型(例如, text、 numeric、 tag)。仔细分析数据并选择最相关的字段以包含在搜索索引中。避免包含不必要的字段,以最小化索引大小并优化搜索性能。
- 利用查询优化技术: RedisSearch 提供各种查询优化技术来提高搜索性能。利用查询过滤器来减少搜索空间并根据特定条件缩小结果范围。
- 此外,请考虑使用词干、同义词和其他特定于语言的分析器来提高搜索准确性。
Redis时间序列
- 选择最佳时间段:根据数据的频率和粒度选择适当的时间间隔。考虑存储空间和查询性能之间的权衡。较小的存储桶提供更详细的数据,但会导致更大的内存使用量。
- 利用查询优化技术:RedisSearch 提供各种查询优化技术来提高搜索性能。使用查询过滤器来减少搜索空间并缩小结果范围。
- 使用压缩和下采样:如果您有高频数据且内存资源有限,请考虑启用压缩和下采样配置。这有助于减少内存使用而不影响数据准确性。
https://www.jdon.com/69677.html
相关文章:
Redis模块的高级使用方式
Redis 模块是Redis的高级功能,允许我们实现特定的自定义数据类型。本质上,模块是一个动态库,可以在启动时或根据命令按需加载到 Redis 中 MODULE LOAD 。模块可以用多种语言编写,包括 C 和 Rust。 我们自己使用 Redis 模块实现新…...

Failed to restart network.service: Unit network.service not found.
执行systemctl restart network命令,报错Failed to restart network.service: Unit network.service not found. 执行 yum install network-scripts命令 再次执行,正常...
wiki.js一个开源知识库系统
1 什么是wiki wiki.js是一个开源Wiki应用程序,官网介绍为: A modern, lightweight and powerful wiki app built on NodeJS 访问Github:github 访问Wike:js.wiki 省流总结 开源知识库平台,和语雀有一样的功能&…...

关于Java抽象类和接口的总结和一点个人的看法
꒰˃͈꒵˂͈꒱ write in front ꒰˃͈꒵˂͈꒱ ʕ̯•͡˔•̯᷅ʔ大家好,我是xiaoxie.希望你看完之后,有不足之处请多多谅解,让我们一起共同进步૮₍❀ᴗ͈ . ᴗ͈ ა 本文由xiaoxieʕ̯•͡˔•̯᷅ʔ 原创 CSDN 如需转载还请通知˶⍤⃝˶个人主页&am…...

vue中ref的用法
vue中ref的用法 在项目中使用ref时有时候直接取值,有时候返回的却是一个数组,不知其中缘由,后查了一下ref用法,所以总结一下. 1.绑定在dom元素上时,用起来与id差不多,通过this.$refs来调用: <div id"passCarEchart" ref"passCarEch…...
【华为OD题库-012】模拟消息队列-Java
题目 让我们来模拟一个消息队列的运作,有一个发布者和若干消费者 ,发布者会在给定的时刻向消息队列发送消息。>若此时消息队列有消费者订阅,这个消息会被发送到订阅的消费者中优先级最高(输入中消费者按优先级升序排列)的一个。>若此时…...

Android修行手册 - 阴影效果的几种实现以及一些特别注意点
点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例点击跳转>软考全系列点击跳转>蓝桥系列点击跳转>ChatGPT和AIGC 👉关于作者 专…...

【星海出品】SDN neutron (五) openvswitch
1、ovs-vswitchd组件是交换机的主要模块,运行在用户态,其主要负责基本的转发逻辑、地址学习、外部物理端口绑定等。还可以运用OVS自带的ovs-ofctl工具采用openflow协议对交换机进行远程配置和管理。 2、ovsdb-server组件是存储OVS的网桥等配置、日志以及…...

springboot整合vue2实现简单的新增删除,整合ECharts实现图表渲染
先看效果图: 1.后端接口 // 查询所有商品信息 // CrossOrigin(origins "*")RequestMapping("/list1")ResponseBodypublic List<Goodsinfo> list1(){List<Goodsinfo> list goodsService.list();return list;}// 删除 // …...

<蓝桥杯软件赛>零基础备赛20周--第5周--杂题-2
报名明年4月蓝桥杯软件赛的同学们,如果你是大一零基础,目前懵懂中,不知该怎么办,可以看看本博客系列:备赛20周合集 20周的完整安排请点击:20周计划 每周发1个博客,共20周(读者可以按…...

数据结构哈希表(散列)Hash,手写实现(图文推导)
目录 一、介绍 二、哈希数据结构 三、✍️实现哈希散列 1. 哈希碰撞💥 2. 拉链寻址⛓️ 3. 开放寻址⏩ 4. 合并散列 一、介绍 哈希表,也被称为散列表,是一种重要的数据结构。它通过将关键字映射到一个表中的位置来直接访问记录&#…...

【嵌入式设计】Main Memory:SPM 便签存储器 | 缓存锁定 | 读取 DRAM 内存 | DREM 猝发(Brust)
目录 0x00 便签存储器(Scratchpad memory) 0x01 缓存锁定(Cache lockdown) 0x02 读取 DRAM 内存 0x03 DREM Banking 0x04 DRAM 猝发(DRAM Burst) 0x00 便签存储器(Scratchpad memory&#…...

dameng数据库数据id decimal类型,精度丢失
问题处理 这一次也是精度丢失,但是问题呢还是不一样,这一次所有的id都被加一了,只有id字段被加一,还有的查询查出来封装成对象之后对象的id字段被减一了,数据库id字段使用的decimal(20,6)&…...
python图神经网络,注意力机制、Transformer模型、目标检测算法、强化学习等
近年来,伴随着以卷积神经网络(CNN)为代表的深度学习的快速发展,人工智能迈入了第三次发展浪潮,AI技术在各个领域中的应用越来越广泛 本文重点为:注意力机制、Transformer模型(BERT、GPT-1/2/3/…...

安装包 amd,amd64, arm,arm64 都有什么区别
现在的安装包也不省心,有各种版本都不知道怎么选。 根据你安装的环境配置。 amd: 32位X86 amd64: 64位X86 arm: 32位ARM arm64: 64位ARM amd64是X86架构的CPU,64位版。amd64又叫X86_64。主流的桌面PC&am…...

Ansible 企业实战详解
一、ansible简介1. ansible是什么2.ansible的特点ansible的架构图 二、ansible 任务执行1、ansible 任务执行模式2、ansible 执行流程3、ansible 命令执行过程 二 .Ansible安装部署1.yum安装2.ansible 程序结构3、ansible配置文件查找顺序4、ansible配置文件5.ansible自动化配置…...
云贝教育 |【技术文章】pg缓存插件介绍
一、pg_buffercache 主要作用是查看pg的共享池中缓存的对象信息 1.1 创建扩展 postgres# create extension pg_buffercache; CREATE EXTENSION 1.2 查看视图pg_buffercache postgres# \d pg_buffercacheView "public.pg_buffercache"Column | Type | Co…...

Kohana框架的安装及部署
Kohana框架的安装及部署 tipsKohana安装以及部署1、重要文件作用说明1.1 /index.php1.2 /application/bootstrap.php 2、项目结构3、路由配置3.1、隐藏项目入口的路由3.2、配置默认路由3.3、配置自定义的路由(Controller目录下的控制器)3.4、配置自定义的路由(Controller/direc…...
无重复字符的最长子串 Golang leecode_3
刚开始的思路,先不管效率,跑出来再说,然后再进行优化。然后就有了下面的暴力代码: func lengthOfLongestSubstring(s string) int {// count 用来记录当前最长子串长度var count int// flag 用来对下面两个 if 语句分流var flag …...

Vue项目的学习一
1、Vue项目里面的.js文件里面对象添加属性 例如:在对象:row,需要在对象row里面添加一个属性状态:type,使用里面的Vue.set函数 Vue.set(参数1,参数2,参数3) Vue.set(row,type,false)解析: 参数1࿱…...

微信小程序之bind和catch
这两个呢,都是绑定事件用的,具体使用有些小区别。 官方文档: 事件冒泡处理不同 bind:绑定的事件会向上冒泡,即触发当前组件的事件后,还会继续触发父组件的相同事件。例如,有一个子视图绑定了b…...

dedecms 织梦自定义表单留言增加ajax验证码功能
增加ajax功能模块,用户不点击提交按钮,只要输入框失去焦点,就会提前提示验证码是否正确。 一,模板上增加验证码 <input name"vdcode"id"vdcode" placeholder"请输入验证码" type"text&quo…...
VTK如何让部分单位不可见
最近遇到一个需求,需要让一个vtkDataSet中的部分单元不可见,查阅了一些资料大概有以下几种方式 1.通过颜色映射表来进行,是最正规的做法 vtkNew<vtkLookupTable> lut; //值为0不显示,主要是最后一个参数,透明度…...

使用Spring AI和MCP协议构建图片搜索服务
目录 使用Spring AI和MCP协议构建图片搜索服务 引言 技术栈概览 项目架构设计 架构图 服务端开发 1. 创建Spring Boot项目 2. 实现图片搜索工具 3. 配置传输模式 Stdio模式(本地调用) SSE模式(远程调用) 4. 注册工具提…...

Python Ovito统计金刚石结构数量
大家好,我是小马老师。 本文介绍python ovito方法统计金刚石结构的方法。 Ovito Identify diamond structure命令可以识别和统计金刚石结构,但是无法直接输出结构的变化情况。 本文使用python调用ovito包的方法,可以持续统计各步的金刚石结构,具体代码如下: from ovito…...
第7篇:中间件全链路监控与 SQL 性能分析实践
7.1 章节导读 在构建数据库中间件的过程中,可观测性 和 性能分析 是保障系统稳定性与可维护性的核心能力。 特别是在复杂分布式场景中,必须做到: 🔍 追踪每一条 SQL 的生命周期(从入口到数据库执行)&#…...

Qt的学习(二)
1. 创建Hello Word 两种方式,实现helloworld: 1.通过图形化的方式,在界面上创建出一个控件,显示helloworld 2.通过纯代码的方式,通过编写代码,在界面上创建控件, 显示hello world; …...

RabbitMQ 各类交换机
为什么要用交换机? 交换机用来路由消息。如果直发队列,这个消息就被处理消失了,那别的队列也需要这个消息怎么办?那就要用到交换机 交换机类型 1,fanout:广播 特点 广播所有消息:将消息…...
比特币:固若金汤的数字堡垒与它的四道防线
第一道防线:机密信函——无法破解的哈希加密 将每一笔比特币交易比作一封在堡垒内部传递的机密信函。 解释“哈希”(Hashing)就是一种军事级的加密术(SHA-256),能将信函内容(交易细节…...
Java求职者面试指南:Spring、Spring Boot、Spring MVC与MyBatis技术点解析
Java求职者面试指南:Spring、Spring Boot、Spring MVC与MyBatis技术点解析 第一轮:基础概念问题 请解释Spring框架的核心容器是什么?它的作用是什么? 程序员JY回答:Spring框架的核心容器是IoC容器(控制反转…...