当前位置: 首页 > news >正文

多级缓存快速上手

 哈喽~大家好,这篇来看看多级缓存。

 🥇个人主页:个人主页​​​​​             

🥈 系列专栏:【微服务】       

🥉与这篇相关的文章:            

JAVA进程和线程JAVA进程和线程-CSDN博客
HttpClient 入门使用示例HttpClient 入门使用示例-CSDN博客
Spring Task 快速入门Spring Task 快速入门-CSDN博客

目录

一、前言

1、什么是多级缓存?

2、集群模式

3、前期准备

二、Caffeine

1、什么是Caffeine?

2、缓存使用的基本API

2.1、基于大小设置驱逐策略

2.2、基于时间设置驱逐策略

三、实现多级缓存

1、前期准备

2、反向代理流程

3、OpenResty监听请求

4、代码解析

4.1、获取参数的API

4.2、查询Tomcat

4.3、CJSON工具类

4.4、基于ID负载均衡

4.5、Redis缓存预热

四、缓存同步

1、数据同步策略

2、监听Canal


一、前言

1、什么是多级缓存?

传统的缓存策略一般是请求到达Tomcat后,先查询Redis,如果未命中则查询数据库,这个是没有问题的,但是这存在一些问题(请求要经过Tomcat处理,Tomcat的性能成为整个系统的瓶颈 ;Redis缓存失效时,大量的数据操作会对数据库产生冲击 )。

那么多级缓存就是充分利用请求处理的每个环节,分别添加缓存,减轻Tomcat压力,提升服务性能。

  • 浏览器访问静态资源时,优先读取浏览器本地缓存

  • 访问非静态资源(ajax查询数据)时,访问服务端

  • 请求到达Nginx后,优先读取Nginx本地缓存

  • 如果Nginx本地缓存未命中,则去直接查询Redis(不经过Tomcat)

  • 如果Redis查询未命中,则查询Tomcat

  • 请求进入Tomcat后,优先查询JVM进程缓存

  • 如果JVM进程缓存未命中,则查询数据库

在多级缓存架构中,nginx是一个编写业务的Web服务器,不是作为反向代理的服务器了。

2、集群模式

也就是说,nginx与tomcat服务要部署为集群模式。

3、前期准备

准备好需要的素材,部署好nginx(注:将其拷贝到一个非中文目录下 ),打开conf里面的nginx.conf配置文件,编写好关键配置(nginx集群的ip地址:端口号;监听/api路径,反向代理到nginx集群)。

此时 192.168.227.131 是我虚拟机的ip地址(这里你写的时候记得换上自己的)

二、Caffeine

1、什么是Caffeine?

Caffeine是一个基于Java8开发的,提供了近乎最佳命中率的高性能的本地缓存库。目前Spring内部的缓存使用的就是Caffeine。GitHub地址:GitHub - ben-manes/caffeine: A high performance caching library for Java

缓存在日常开发中启动至关重要的作用 ,能大量减少对数据库的访问,减少数据库的压力 ,我们把缓存分为两类:

  • 分布式缓存,例如Redis:

    • 优点:存储容量更大、可靠性更好、可以在集群间共享

    • 缺点:访问缓存有网络开销

    • 场景:缓存数据量较大、可靠性要求较高、需要在集群间共享

  • 进程本地缓存,例如HashMap、GuavaCache:

    • 优点:读取本地内存,没有网络开销,速度更快

    • 缺点:存储容量有限、可靠性较低、无法共享

    • 场景:性能要求较高,缓存数据量较小

我们的思路是:当我们的请求到nginx中,首先先查询本地缓存,当本地缓存没有时,再去查询redis,redis没有时,再去查询jvm进程,当这些都没有命中时,再最后查数据库。

2、缓存使用的基本API

@Test
void testBasicOps() {// 构建cache对象Cache<String, String> cache = Caffeine.newBuilder().build();// 存数据cache.put("gf", "ddf");// 取数据String gf = cache.getIfPresent("gf");System.out.println("gf = " + gf);// 取数据,包含两个参数:// 参数一:缓存的key// 参数二:Lambda表达式,表达式参数就是缓存的key,方法体是查询数据库的逻辑// 优先根据key查询JVM缓存,如果未命中,则执行参数二的Lambda表达式String defaultGF = cache.get("defaultGF", key -> {// 根据key去数据库查询数据return "asdSystem.out.println("defaultGF = " + defaultGF);
}

Caffeine提供了三种缓存驱逐策略:

  • 基于容量:设置缓存的数量上限

    // 创建缓存对象
    Cache<String, String> cache = Caffeine.newBuilder().maximumSize(1) // 设置缓存大小上限为 1.build();

  • 基于时间:设置缓存的有效时间

    // 创建缓存对象
    Cache<String, String> cache = Caffeine.newBuilder()// 设置缓存有效期为 10 秒,从最后一次写入开始计时 .expireAfterWrite(Duration.ofSeconds(10)) .build();
     
  • 基于引用:设置缓存为软引用或弱引用,利用GC来回收缓存数据。性能较差,不建议使用。

2.1、基于大小设置驱逐策略

    @Testvoid testEvictByNum() throws InterruptedException {// 创建缓存对象Cache<String, String> cache = Caffeine.newBuilder()// 设置缓存大小上限为 1.maximumSize(1).build();// 存数据cache.put("gf1", "a");cache.put("gf2", "b");cache.put("gf3", "c");// 延迟10ms,给清理线程一点时间Thread.sleep(10L);// 获取数据System.out.println("gf1: " + cache.getIfPresent("gf1"));System.out.println("gf2: " + cache.getIfPresent("gf2"));System.out.println("gf3: " + cache.getIfPresent("gf3"));}

2.2、基于时间设置驱逐策略

    @Testvoid testEvictByTime() throws InterruptedException {// 创建缓存对象Cache<String, String> cache = Caffeine.newBuilder().expireAfterWrite(Duration.ofSeconds(1)) // 设置缓存有效期为 10 秒.build();// 存数据cache.put("gf", "aaa");// 获取数据System.out.println("gf: " + cache.getIfPresent("gf"));// 休眠一会儿Thread.sleep(1200L);System.out.println("gf: " + cache.getIfPresent("gf"));}

三、实现多级缓存

1、前期准备

多级缓存的实现离不开Nginx编程,而Nginx编程又离不开OpenResty。

下载与安装步骤这里就不做过多的描述了,OpenResty底层是基于Nginx的,查看OpenResty目录的nginx目录,所以运行方式与nginx基本一致:

# 启动nginx
nginx
# 重新加载配置
nginx -s reload
# 停止
nginx -s stop

修改/usr/local/openresty/nginx/conf/nginx.conf文件,内容如下:


#user  nobody;
worker_processes  1;
error_log  logs/error.log;events {worker_connections  1024;
}http {include       mime.types;default_type  application/octet-stream;sendfile        on;keepalive_timeout  65;server {listen       8081;server_name  localhost;location / {root   html;index  index.html index.htm;}error_page   500 502 503 504  /50x.html;location = /50x.html {root   html;}}
}

2、反向代理流程

打开案例,他的请求路径是这个:【微服务】       

请求地址是localhost,端口是80,就被windows上安装的Nginx服务给接收到了。然后代理给了OpenResty集群,这就是ip为:192.168.227.131。

3、OpenResty监听请求

OpenResty的很多功能都依赖于其目录下的Lua库,需要在nginx.conf中指定依赖库的目录,

修改/usr/local/openresty/nginx/conf/nginx.conf文件,在其中的http下面,添加下面代码:

#lua 模块
lua_package_path "/usr/local/openresty/lualib/?.lua;;";
#c模块     
lua_package_cpath "/usr/local/openresty/lualib/?.so;;";  

监听/api/item路径

修改/usr/local/openresty/nginx/conf/nginx.conf文件,在nginx.conf的server下面,添加对/api/item这个路径的监听:

location  /api/item {# 默认的响应类型default_type application/json;# 响应结果由lua/item.lua文件来决定content_by_lua_file lua/item.lua;
}

这个监听,就类似于SpringMVC中的@GetMapping("/api/item")做路径映射,而返回类型就是json。

content_by_lua_file lua/item.lua则相当于调用item.lua这个文件,执行其中的业务,把结果返回给用户。相当于java中调用service。

/usr/loca/openresty/nginx目录创建文件夹:lua;在/usr/loca/openresty/nginx/lua文件夹下,新建文件:item.lua。

item.lua代码

-- 导入common函数库
local common = require('common')
local read_http = common.read_http
local read_redis = common.read_redis
-- 导入cjson库
local cjson = require('cjson')
-- 导入item_cache
local item_cache = ngx.shared.item_cache-- 封装查询函数
function read_data(key, expire,  path, params)local var = item_cache:get(key)if not var thenngx.log(ngx.ERR, "本地缓存查询失败,尝试查询redis, key: ", key)-- 查询redis缓存var = read_redis("127.0.0.1", 6379, key)-- 判断查询结果if not var thenngx.log(ngx.ERR, "redis查询失败,尝试查询http, key: ", key)-- redis查询失败,去查询httpvar = read_http(path, params)endend-- 查询成功,根据不同的数据设置不同的缓存时间,并且写入到本地缓存item_cache:set(key, var, expire)-- 返回数据return var
end-- 获取路径参数
local id = ngx.var[1]-- 查询商品信息
local itemJSON = read_data("item:id:" .. id, 1800,  "/item/" .. id, nil)
-- 查询库存信息
local stockJSON = read_data("item:stock:id:" .. id, 60, "/item/stock/" .. id, nil)-- JSON转化为lua的table
local item = cjson.decode(itemJSON)
local stock = cjson.decode(stockJSON)
-- 组合数据
item.stock = stock.stock
item.sold = stock.sold-- 把item序列化为json 返回结果
ngx.say(cjson.encode(item))

在nginx.cpnf里面添加

		# 添加反向代理,到windows的Java服务# 该指令是用来设置代理服务器的地址,可以是主机名称,IP地址加端口号等形式。location /item {proxy_pass http://tomcat-cluster;}
     upstream tomcat-cluster{hash $request_uri;server 192.168.177.196:8081;server 192.168.177.196:8082;}

common.lua 代码

-- 导入redis
local redis = require("resty.redis")
-- 初始化 redis
local red = redis:new()
red:set_timeouts(1000, 1000, 1000)-- 关闭redis连接的工具方法,其实是放入连接池
local function close_redis(red)local pool_max_idle_time = 10000 -- 连接的空闲时间,单位是毫秒local pool_size = 100 --连接池大小local ok, err = red:set_keepalive(pool_max_idle_time, pool_size)if not ok thenngx.log(ngx.ERR, "放入redis连接池失败: ", err)end
end-- 查询redis的方法 ip和port是redis地址,key是查询的key
local function read_redis(ip, port, key)-- 获取一个连接local ok, err = red:connect(ip, port)if not ok thenngx.log(ngx.ERR, "连接redis失败 : ", err)return nilend-- 查询redislocal resp, err = red:get(key)-- 查询失败处理if not resp thenngx.log(ngx.ERR, "查询Redis失败: ", err, ", key = " , key)end--得到的数据为空处理if resp == ngx.null thenresp = nilngx.log(ngx.ERR, "查询Redis数据为空, key = ", key)endclose_redis(red)return resp
end-- 封装函数,发送http请求,并解析响应( ngx.location.capture)
local function read_http(path, params)local resp = ngx.location.capture(path,{method = ngx.HTTP_GET,args = params,})if not resp then-- 记录错误信息,返回404ngx.log(ngx.ERR, "http请求查询失败, path: ", path , ", args: ", args)ngx.exit(404)endreturn resp.body
end
-- 将方法导出
local _M = {  read_http = read_http,read_redis = read_redis
}  
return _M

然后重新加载配置:nginx -s reload。

4、代码解析

4.1、获取参数的API

OpenResty中提供了一些API用来获取不同类型的前端请求参数:

location ~ /api/item/(\d+) {
    # 默认的响应类型
    default_type application/json;
    # 响应结果由lua/item.lua文件来决定
    content_by_lua_file lua/item.lua;
}

里面的  ~ /api/item/(\d+) 对应的就是 http://localhost/api/item/10003 (前端发来的路径,这里拿到了商品的id)

4.2、查询Tomcat

拿到商品ID后,本应去缓存中查询商品信息,不过目前我们还未建立nginx、redis缓存。因此,这里我们先根据商品id去tomcat查询商品信息。

发送http请求的API

举个例子:

local resp = ngx.location.capture("/path",{method = ngx.HTTP_GET,   -- 请求方式args = {a=1,b=2},  -- get方式传参数
})

返回的响应内容包括:

  • resp.status:响应状态码

  • resp.header:响应头,是一个table

  • resp.body:响应体,就是响应数据

注意:这里的path是路径,并不包含IP和端口。这个请求会被nginx内部的server监听并处理。

但是我们希望这个请求发送到Tomcat服务器,所以还需要编写一个server来对这个路径做反向代理:

 location /path {# 这里是windows电脑的ip和Java服务端口,需要确保windows防火墙处于关闭状态proxy_pass http://你自己的ip:8081; }

在item.lua文件当中,有这一串:

-- 引入自定义common工具模块,返回值是common中返回的 _M
local common = require("common")
-- 从 common中获取read_http这个函数
local read_http = common.read_http
-- 获取路径参数
local id = ngx.var[1]
-- 根据id查询商品
local itemJSON = read_http("/item/".. id, nil)
-- 根据id查询商品库存
local itemStockJSON = read_http("/item/stock/".. id, nil)
ngx.say(itemStockJSON )

他的作用是接受到请求路径,然后根据id来查询数据库,返回json数据。

里查询到的结果是json字符串,并且包含商品、库存两个json字符串,页面最终需要的是把两个json拼接为一个json:

这就需要我们先把JSON变为lua的table,完成数据整合后,再转为JSON(序列化与反序列化)。

4.3、CJSON工具类

OpenResty提供了一个cjson的模块用来处理JSON的序列化和反序列化。

举个例子:

引入cjson模块:

local cjson = require "cjson"

序列化:

local obj = {name = 'jack',age = 21
}
-- 把 table 序列化为 json
local json = cjson.encode(obj)

反序列化:

local json = '{"name": "jack", "age": 21}'
-- 反序列化 json为 table
local obj = cjson.decode(json);
print(obj.name)

那么实现Tomcat'查询是:

-- 导入common函数库
local common = require('common')
local read_http = common.read_http
-- 导入cjson库
local cjson = require('cjson')-- 获取路径参数
local id = ngx.var[1]
-- 根据id查询商品
local itemJSON = read_http("/item/".. id, nil)
-- 根据id查询商品库存
local itemStockJSON = read_http("/item/stock/".. id, nil)-- JSON转化为lua的table
local item = cjson.decode(itemJSON)
local stock = cjson.decode(stockJSON)-- 组合数据
item.stock = stock.stock
item.sold = stock.sold-- 把item序列化为json 返回结果
ngx.say(cjson.encode(item))

4.4、基于ID负载均衡

刚才的代码中,我们的tomcat是单机部署。而实际开发中,tomcat一定是集群模式,因此,OpenResty需要对tomcat集群做负载均衡。

如何做?

如果能让同一个商品,每次查询时都访问同一个tomcat服务,那么JVM缓存就一定能生效了。

也就是说,我们需要根据商品id做负载均衡,而不是轮询。

思路

nginx根据请求路径做hash运算,把得到的数值对tomcat服务的数量取余,余数是几,就访问第几个服务,实现负载均衡。

举个例子

  • 我们的请求路径是 /item/10001

  • tomcat总数为2台(8081、8082)

  • 对请求路径/item/1001做hash运算求余的结果为1

  • 则访问第一个tomcat服务,也就是8081

只要id不变,每次hash运算结果也不会变,那就可以保证同一个商品,一直访问同一个tomcat服务,确保JVM缓存生效。

在nginx.conf文件里面添加这一段(hash $request_uri;)

     upstream tomcat-cluster{hash $request_uri;server 192.168.177.196:8081;server 192.168.177.196:8082;}

然后,修改对tomcat服务的反向代理,目标指向tomcat集群:

location /item {proxy_pass http://tomcat-cluster;
}

重新加载OpenResty

nginx -s reload

4.5、Redis缓存预热

Redis缓存会面临冷启动问题:

冷启动:服务刚刚启动时,Redis中并没有缓存,如果所有商品数据都在第一次查询时添加缓存,可能会给数据库带来较大压力。

缓存预热:在实际开发中,我们可以利用大数据统计用户访问的热点数据,在项目启动时将这些热点数据提前查询并保存到Redis中。

由于数据较少所以这里将所有的数据都存入缓存中。

具体代码

@Component
public class RedisHandler implements InitializingBean {@Autowiredprivate StringRedisTemplate redisTemplate;@Autowiredprivate IItemService itemService;@Autowiredprivate IItemStockService itemStockService;/*** Jackson提供了ObjectMapper来供程序员“定制化控制”序列化、反序列化的过程。* objectMapper在调用writeValue()序列化 或 调用readValue()反序列化方法之前,* 往往需要设置 ObjectMapper 的相关配置信息,这些配置信息作用在 java 对象的所有属性上,* 表示在进行序列化和反序列化时进行一些特殊的处理。*/private static final ObjectMapper MAPPER = new ObjectMapper();@Overridepublic void afterPropertiesSet() throws Exception {// 查询商品List<Item> itemList = itemService.list();// 商品集合序列化,存入redisfor (Item item : itemList) {String itemJson = MAPPER.writeValueAsString(item);redisTemplate.opsForValue().set("item:id:" + item.getId(), itemJson);}// 查询库存List<ItemStock> stockList = itemStockService.list();// 库存集合序列化,存入redisfor (ItemStock stock : stockList) {String stockJson = MAPPER.writeValueAsString(stock);redisTemplate.opsForValue().set("item:stock:id:" + stock.getId(), stockJson);}}public void save(Item item){try {String itemJson = MAPPER.writeValueAsString(item);redisTemplate.opsForValue().set("item:id:" + item.getId(), itemJson);} catch (JsonProcessingException e) {throw new RuntimeException(e);}}public void delete(Long id){redisTemplate.delete("item:id:" + id);}}

InitializingBean接口为bean提供了初始化方法的方式,它只包括afterPropertiesSet方法,凡是继承该接口的类,在初始化bean的时候都会执行该方法。

ObjectMapper:Jackson提供了ObjectMapper来供程序员“定制化控制”序列化、反序列化的过程。objectMapper在调用writeValue()序列化 或 调用readValue()反序列化方法之前,往往需要设置 ObjectMapper 的相关配置信息,这些配置信息作用在 java 对象的所有属性上,表示在进行序列化和反序列化时进行一些特殊的处理。

四、缓存同步

大多数情况下,浏览器查询到的都是缓存数据,当我们管理员修改数据时,缓存没有及时更新,这就会出大问题了。

所以我们必须保证数据库数据、缓存数据的一致性,这就是缓存与数据库的同步。

1、数据同步策略

设置有效期:给缓存设置有效期,到期后自动删除。再次查询时更新

  • 优势:简单、方便

  • 缺点:时效性差,缓存过期之前可能不一致

  • 场景:更新频率较低,时效性要求低的业务

同步双写:在修改数据库的同时,直接修改缓存

  • 优势:时效性强,缓存与数据库强一致

  • 缺点:有代码侵入,耦合度高;

  • 场景:对一致性、时效性要求较高的缓存数据

异步通知:修改数据库时发送事件通知,相关服务监听到通知后修改缓存数据

  • 优势:低耦合,可以同时通知多个缓存服务

  • 缺点:时效性一般,可能存在中间不一致状态

  • 场景:时效性要求一般,有多个服务需要同步

这里我们使用Canal(基于Canal的通知 )

2、监听Canal

Canal提供了各种语言的客户端,当Canal监听到binlog变化时,会通知Canal的客户端。

我们可以利用Canal提供的Java客户端,监听Canal通知消息。当收到变化的消息时,完成对缓存的更新。

引入依赖

<dependency><groupId>top.javatool</groupId><artifactId>canal-spring-boot-starter</artifactId><version>1.2.1-RELEASE</version>
</dependency>

编写配置

canal:destination: heima # canal的集群名字,要与安装canal时设置的名称一致server: 192.168.150.101:11111 # canal服务地址

修改实体类

@Data
@TableName("tb_item")
public class Item {@TableId(type = IdType.AUTO)@Idprivate Long id;//商品id@Column(name = "name")private String name;//商品名称private String title;//商品标题private Long price;//价格(分)private String image;//商品图片private String category;//分类名称private String brand;//品牌名称private String spec;//规格private Integer status;//商品状态 1-正常,2-下架private Date createTime;//创建时间private Date updateTime;//更新时间@TableField(exist = false)@Transientprivate Integer stock;@TableField(exist = false)@Transientprivate Integer sold;
}

@TableName("tb_item"):要监听的表名

@Id:告诉他谁是id(主键)

@Column(name = "name"):当DB里面的字段与实体类对应不上时,用name对应。

@Transient:告诉它,谁不是表中的字段。

编写监听器

通过实现EntryHandler<T>接口编写监听器,监听Canal消息。注意两点:

  • 实现类通过@CanalTable("tb_item")指定监听的表信息

  • EntryHandler的泛型是与表对应的实体类

@CanalTable("tb_item")
@Component
public class ItemHandler implements EntryHandler<Item> {@Autowiredprivate RedisHandler redisHandler;@Autowiredprivate Cache<Long, Item> itemCache;@Overridepublic void insert(Item item) {// 写数据到JVM进程缓存itemCache.put(item.getId(), item);// 写数据到redisredisHandler.saveItem(item);}@Overridepublic void update(Item before, Item after) {// 写数据到JVM进程缓存itemCache.put(after.getId(), after);// 写数据到redisredisHandler.saveItem(after);}@Overridepublic void delete(Item item) {// 删除数据到JVM进程缓存itemCache.invalidate(item.getId());// 删除数据到redisredisHandler.deleteItemById(item.getId());}
}

不积跬步无以至千里,趁年轻,使劲拼,给未来的自己一个交代!向着明天更好的自己前进吧!

相关文章:

多级缓存快速上手

哈喽~大家好&#xff0c;这篇来看看多级缓存。 &#x1f947;个人主页&#xff1a;个人主页​​​​​ &#x1f948; 系列专栏&#xff1a;【微服务】 &#x1f949;与这篇相关的文章&#xff1a; JAVA进程和线程JAVA进程和线程-CSDN博客Http…...

初始React

<!DOCTYPE html> <html> <head> <meta charset"UTF-8" /> <title>React</title> </head> <body> 了解React <!-- React是一个用于构建web和原生态交互界面的库 相对于传统DOM开发优势&#xff1a;组件化开发…...

2.5 逆矩阵

一、逆矩阵的注释 假设 A A A 是一个方阵&#xff0c;其逆矩阵 A − 1 A^{-1} A−1 与它的大小相同&#xff0c; A − 1 A I A^{-1}AI A−1AI。 A A A 与 A − 1 A^{-1} A−1 会做相反的事情。它们的乘积是单位矩阵 —— 对向量无影响&#xff0c;所以 A − 1 A x x A^{…...

物流实时数仓:数仓搭建(ODS)

系列文章目录 物流实时数仓&#xff1a;采集通道搭建 物流实时数仓&#xff1a;数仓搭建 文章目录 系列文章目录前言一、IDEA环境准备1.pom.xml2.目录创建 二、代码编写1.log4j.properties2.CreateEnvUtil.java3.KafkaUtil.java4.OdsApp.java 三、代码测试总结 前言 现在我们…...

【ARM 嵌入式 编译 Makefile 系列 18 -- Makefile 中的 export 命令详细介绍】

文章目录 Makefile 中的 export 命令详细介绍Makefile 使用 export导出与未导出变量的区别示例&#xff1a;导出变量以供子 Makefile 使用 Makefile 中的 export 命令详细介绍 在 Makefile 中&#xff0c;export 命令用于将变量从 Makefile 导出到由 Makefile 启动的子进程的环…...

【opencv】计算机视觉:停车场车位实时识别

目录 目标 整体流程 背景 详细讲解 目标 我们想要在一个实时的停车场监控视频中&#xff0c;看看要有多少个车以及有多少个空缺车位。然后我们可以标记空的&#xff0c;然后来车之后&#xff0c;实时告诉应该停在那里最方便、最近&#xff01;&#xff01;&#xff01;实现…...

播放器开发(三):FFmpeg与SDL环境配置

学习课题&#xff1a;逐步构建开发播放器【QT5 FFmpeg6 SDL2】 环境配置 我这边的是使用macOS&#xff1b;IDE用的是CLion&#xff1b;CMake构建&#xff0c;除了创建项目步骤、CMakeLists文件有区别之外的代码层面不会有太大区别。 配置上只添加一下CMakeLists中FFmpeg和SD…...

KubeVela核心控制器原理浅析

前言 在学习 KubeVela 的核心控制器之前&#xff0c;我们先简单了解一下 KubeVela 的相关知识。 KubeVela 本身是一个应用交付与管理控制平面&#xff0c;它架在 Kubernetes 集群、云平台等基础设施之上&#xff0c;通过开放应用模型来对组件、云服务、运维能力、交付工作流进…...

迎接“全全闪”时代 XSKY星辰天合发布星海架构和星飞产品

11 月 17 日消息&#xff0c;北京市星辰天合科技股份有限公司&#xff08;简称&#xff1a;XSKY星辰天合&#xff09;在北京首钢园举办了主题为“星星之火”的 XSKY 星海全闪架构暨星飞存储发布会。 &#xff08;图注&#xff1a;XSKY星辰天合 CEO 胥昕&#xff09; XSKY星辰天…...

[架构相关]基础架构设计原则

基础架构设计原则 文章目录 基础架构设计原则一、可用性&#xff08;Availability&#xff09;1.1、引入冗余1.2、负载均衡1.3、故障转移1.4、备份和恢复策略 二、可扩展性&#xff08;Scalability&#xff09;2.1 水平扩展2.2 垂直扩展2.3 弹性扩展 三、可靠性&#xff08;Rel…...

测试在 Oracle 下直接 rm dbf 数据文件并重启数据库

创建一个新的表空间并创建新的用户&#xff0c;指定新表空间为新用户的默认表空间 create tablespace zzw datafile /oradata/cesdb/zzw01.dbf size 10m;zzw用户已经创建过&#xff0c;这里修改其默认表空间 alter user zzw quota unlimited on zzw; alter user zzw default …...

【开源】基于JAVA的计算机机房作业管理系统

项目编号&#xff1a; S 017 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S017&#xff0c;文末获取源码。} 项目编号&#xff1a;S017&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 登录注册模块2.2 课程管理模块2.3 课…...

Ubuntu 配置静态 IP

Ubuntu 18 开始可以使用netplan配置网络。配置文件位于/etc/netplan/xxx.yaml中&#xff0c;netplan默认是使用NetworkManager来配置网卡信息的。 修改配置文件&#xff1a; 1、打开文件编辑&#xff1a;sudo vi 01-network-manager-all.yaml原文件内容如下&#xff1a;netwo…...

Spring Cloud实战 |分布式系统的流量控制、熔断降级组件Sentinel如何使用

专栏集锦&#xff0c;大佬们可以收藏以备不时之需 Spring Cloud实战专栏&#xff1a;https://blog.csdn.net/superdangbo/category_9270827.html Python 实战专栏&#xff1a;https://blog.csdn.net/superdangbo/category_9271194.html Logback 详解专栏&#xff1a;https:/…...

第六届 传智杯初赛B组

文章目录 A. 字符串拼接&#x1f37b; AC code B. 最小差值&#x1f37b; AC code C. 红色和紫色&#x1f37b; AC code D. abb&#x1f37b; AC code E. kotori和素因子&#x1f37b; AC code F. 红和蓝&#x1f37b; AC code &#x1f970; Tips&#xff1a;AI可以把代码从 j…...

文档向量化工具(二):text2vec介绍

目录 前言 text2vec开源项目 核心能力 文本向量表示模型 本地试用 安装依赖 下载模型到本地&#xff08;如果你的网络能直接从huggingface上拉取文件&#xff0c;可跳过&#xff09; ​运行试验代码 前言 在上一篇文章中介绍了&#xff0c;如何从不同格式的文件里提取…...

vscode中pylance无法显示outline无法跳转

当打开的workspce中有较多的文件时&#xff0c;pylance需要分析的文件太多&#xff0c;导致卡住&#xff0c;无法分析到对应的python文件 常见的情况是&#xff0c;当我们在workspace中包含了data文件夹&#xff08;通常是通过软连接方式把数据集链接过来&#xff09;&#xf…...

番外篇之通讯录

前言&#xff1a;用到的知识点有枚举、结构体、数组&#xff0c;快速排序&#xff08;用的名字排序&#xff09; 下面是测试函数&#xff1a; test.c #define _CRT_SECURE_NO_WARNINGS 1 #include"contact.h" void menu() {printf("*************************…...

学生信息管理系统程序Python

系统主界面 在该界面中可以选择要使用功能对应的菜单进行不同的操作。在选择功能菜单时&#xff0c;有两种方法&#xff0c; 一种是输入1&#xff0c;另一种是按下键盘上的↑或↓方向键进行选择。这两种方法的结果是一样的&#xff0c;所以使用哪种方法都可以。 &#xff08;…...

[js] for forEach for of 循环里await关键字的用法

1、for&#xff1a;循环中使用await的写法&#xff08;生效&#xff09; async function loop(){for( let i0; i<array.length; i ){let datas await getDatas()break} }2、forEach&#xff1a;循环中使用await的写法&#xff08;不生效&#xff09;&#xff1a; array.f…...

HTML 语义化

目录 HTML 语义化HTML5 新特性HTML 语义化的好处语义化标签的使用场景最佳实践 HTML 语义化 HTML5 新特性 标准答案&#xff1a; 语义化标签&#xff1a; <header>&#xff1a;页头<nav>&#xff1a;导航<main>&#xff1a;主要内容<article>&#x…...

可靠性+灵活性:电力载波技术在楼宇自控中的核心价值

可靠性灵活性&#xff1a;电力载波技术在楼宇自控中的核心价值 在智能楼宇的自动化控制中&#xff0c;电力载波技术&#xff08;PLC&#xff09;凭借其独特的优势&#xff0c;正成为构建高效、稳定、灵活系统的核心解决方案。它利用现有电力线路传输数据&#xff0c;无需额外布…...

DAY 47

三、通道注意力 3.1 通道注意力的定义 # 新增&#xff1a;通道注意力模块&#xff08;SE模块&#xff09; class ChannelAttention(nn.Module):"""通道注意力模块(Squeeze-and-Excitation)"""def __init__(self, in_channels, reduction_rat…...

【Redis技术进阶之路】「原理分析系列开篇」分析客户端和服务端网络诵信交互实现(服务端执行命令请求的过程 - 初始化服务器)

服务端执行命令请求的过程 【专栏简介】【技术大纲】【专栏目标】【目标人群】1. Redis爱好者与社区成员2. 后端开发和系统架构师3. 计算机专业的本科生及研究生 初始化服务器1. 初始化服务器状态结构初始化RedisServer变量 2. 加载相关系统配置和用户配置参数定制化配置参数案…...

【解密LSTM、GRU如何解决传统RNN梯度消失问题】

解密LSTM与GRU&#xff1a;如何让RNN变得更聪明&#xff1f; 在深度学习的世界里&#xff0c;循环神经网络&#xff08;RNN&#xff09;以其卓越的序列数据处理能力广泛应用于自然语言处理、时间序列预测等领域。然而&#xff0c;传统RNN存在的一个严重问题——梯度消失&#…...

Cinnamon修改面板小工具图标

Cinnamon开始菜单-CSDN博客 设置模块都是做好的&#xff0c;比GNOME简单得多&#xff01; 在 applet.js 里增加 const Settings imports.ui.settings;this.settings new Settings.AppletSettings(this, HTYMenusonichy, instance_id); this.settings.bind(menu-icon, menu…...

HBuilderX安装(uni-app和小程序开发)

下载HBuilderX 访问官方网站&#xff1a;https://www.dcloud.io/hbuilderx.html 根据您的操作系统选择合适版本&#xff1a; Windows版&#xff08;推荐下载标准版&#xff09; Windows系统安装步骤 运行安装程序&#xff1a; 双击下载的.exe安装文件 如果出现安全提示&…...

土地利用/土地覆盖遥感解译与基于CLUE模型未来变化情景预测;从基础到高级,涵盖ArcGIS数据处理、ENVI遥感解译与CLUE模型情景模拟等

&#x1f50d; 土地利用/土地覆盖数据是生态、环境和气象等诸多领域模型的关键输入参数。通过遥感影像解译技术&#xff0c;可以精准获取历史或当前任何一个区域的土地利用/土地覆盖情况。这些数据不仅能够用于评估区域生态环境的变化趋势&#xff0c;还能有效评价重大生态工程…...

Device Mapper 机制

Device Mapper 机制详解 Device Mapper&#xff08;简称 DM&#xff09;是 Linux 内核中的一套通用块设备映射框架&#xff0c;为 LVM、加密磁盘、RAID 等提供底层支持。本文将详细介绍 Device Mapper 的原理、实现、内核配置、常用工具、操作测试流程&#xff0c;并配以详细的…...

Bean 作用域有哪些?如何答出技术深度?

导语&#xff1a; Spring 面试绕不开 Bean 的作用域问题&#xff0c;这是面试官考察候选人对 Spring 框架理解深度的常见方式。本文将围绕“Spring 中的 Bean 作用域”展开&#xff0c;结合典型面试题及实战场景&#xff0c;帮你厘清重点&#xff0c;打破模板式回答&#xff0c…...