Redis——多级缓存

JVM进程缓存

为了演示多级缓存,这里先导入一个商品管理的案例,其中包含商品的CRUD功能。将来会给查询商品添加多级缓存。

导入Demo数据

1.安装mysql

后期做数据同步需要用到MySQL的主从功能,所以需要在虚拟机中,利用Docker来运行一个MySQL容器。

1.1准备目录

为了方便后期配置MySQL,我们先准备两个目录,用于挂载容器的数据和配置文件目录:

# 进入/docker_volume目录
cd /docker_volume
# 创建文件夹
mkdir mysql
# 进入mysql目录
cd mysql
1.2.运行命令

进入mysql目录后,执行下面的Docker命令:

这里mysql容器版本需要自己根据自己的容器版本准备

docker run \
 -p 3306:3306 \
 --name mysql \
 -v $PWD/conf:/etc/mysql/conf.d \
 -v $PWD/logs:/logs \
 -v $PWD/data:/var/lib/mysql \
 -e MYSQL_ROOT_PASSWORD=123 \
 --privileged \
 -itd \
 mysql:5.7.25
1.3修改配置

在/docker_volume/mysql/conf目录添加一个my.cnf文件,作为mysql的配置文件:  

# 创建文件
touch /docker_volume/mysql/conf/my.cnf

 文件的内容如下:

[mysqld]
skip-name-resolve
character_set_server=utf8
datadir=/var/lib/mysql
server-id=1000
1.4重启
docker restart mysql

2.导入SQL

利用课前资料里面的sql文件,在idea里面连接mysql进行导入.

其中包含两张表:

  • tb_item:商品表,包含商品的基本信息

  • tb_item_stock:商品库存表,包含商品的库存信息

之所以将库存分离出来,是因为库存是更新比较频繁的信息,写操作较多。而其他信息修改的频率非常低。

导入Demo工程

使用给的资料里的工程进行导入.

 

 导入商品查询页面

 

 完整内容如下


#user  nobody;
worker_processes  1;

events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    sendfile        on;
    #tcp_nopush     on;
    keepalive_timeout  65;
    # nginx的业务集群,nginx本地缓存,redis缓存,tomcat查询
    upstream nginx-cluster{
        server 192.168.150.101:8081;
    }
    server {
        listen       80;
        server_name  localhost;

	location /api {
            proxy_pass http://nginx-cluster;
        }

        location / {
            root   html;
            index  index.html index.htm;
        }

        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }
}

初识Caffeine

分布式缓存和进程本地缓存的对比,进程缓存只能在本地,不能和别的tomcat共享。

示例

缓存的驱逐策略

基于容量清理的是基于LRU策略,最近最少使用的。

    /*
     基于大小设置驱逐策略:
     */
    @Test
    void testEvictByNum() throws InterruptedException {
        // 创建缓存对象
        Cache<String, String> cache = Caffeine.newBuilder()
                // 设置缓存大小上限为 1
                .maximumSize(1)
                .build();
        // 存数据
        cache.put("gf1", "柳岩");
        cache.put("gf2", "范冰冰");
        cache.put("gf3", "迪丽热巴");
        // 延迟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"));
    }

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

 

实现进程缓存

 这里的本地缓存真实点的场景是存点什么,商品数据这样存那么多机器很容易就遇到不一致了.

准备两个配置类

@Configuration
public class CaffeineConfig {
    @Bean
    public Cache<Long, Item> itemCache(){
        return Caffeine.newBuilder()
                .initialCapacity(100)
                .maximumSize(10_000)
                .build();
    }
    @Bean
    public Cache<Long, ItemStock> stockCache(){
        return Caffeine.newBuilder()
                .initialCapacity(100)
                .maximumSize(10_000)
                .build();
    }
}

 改造业务代码,这里要注入两个bean和使用现成的api在查询数据库前先查询本地缓存。

    @Autowired
    private Cache<Long,Item> itemCache;

    @Autowired
    private Cache<Long,ItemStock> stockCache;
  

  @GetMapping("/{id}")
    public Item findById(@PathVariable("id") Long id){
       return itemCache.get(id,key->itemService.query() //这里key就是id,因为lamda表达式需要重新命名,不然会冲突。
                .ne("status",3).eq("id",key)
                .one()
        );
    }

    @GetMapping("/stock/{id}")
    public ItemStock findStockById(@PathVariable("id") Long id){

        return stockCache.get(id,key->stockService.getById(key));
    }

测试

第一次查询可以看见有sql语句查询了数据库

第二次查询就没有查询数据库了 ,控制台一篇空白

Lua语法入门

要配置查询nginx缓存需要使用lua语言。

初识Lua

这玩意可以写外挂脚本,再配合修改器使用。并且redis里面也是支持lua的。

在ubuntu里面需要先安装Lua环境。 

sudo apt install Lua5.1

变量和循环

条件控制、函数

多级缓存

安装OpenResty

拉取镜像

docker pull openresty/openresty

启动

docker run --name openresty -p 80:80 -d openresty/openresty

复制配置文件

1.创建宿主机目录

mkdir /usr/local/openresty
cd /usr/local/openresty
# 存放nginx的配置文件
mkdir conf
# 存放lua脚本
mkdir lua

2、拷贝容器中nginx配置文件到宿主机目录

docker cp openresty:/usr/local/openresty/nginx/conf/nginx.conf /usr/local/openresty/conf/
# 拷贝lua库
docker cp openresty:/usr/local/openresty/lualib /usr/local/openresty/

删除容器,启动新容器

### 删除 openresty 容器
docker rm -f openresty

### 配置启动 openresty,配置自动启动
docker run -p 80:80 -p 8081:8081 \
--name openresty --restart always \
-v /usr/local/openresty/conf/nginx.conf:/usr/local/openresty/nginx/conf/nginx.conf \
-v /etc/localtime:/etc/localtime \
openresty/openresty

# 或者修改启动端口,去掉自动启动,增加lua脚本映射目录
docker run --name openresty \
-v /usr/local/openresty/conf/nginx.conf:/usr/local/openresty/nginx/conf/nginx.conf \
-v /usr/local/openresty/lua/:/usr/local/openresty/nginx/lua \
-v /usr/local/openresty/lualib/:/usr/local/openresty/lualib \
-p 80:80 -p 8081:8081 -d openresty/openresty

 然后访问虚拟机的ip可以得到如下页面

nginx的默认配置文件注释太多,影响后续我们的编辑,这里将nginx.conf中的注释部分删除,保留有效部分。

修改`/usr/local/openresty/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;
        }
    }
}

OpenResty快速入门

上面已经配置好了 

#lua 模块
	lua_package_path "/usr/local/openresty/lualib/?.lua;;";
	#c模块     
	lua_package_cpath "/usr/local/openresty/lualib/?.so;;";  
		location /api/item {
            # 默认的响应类型
            default_type application/json;
            # 响应结果有lua/item.lua文件来决定
            content_by_lua_file lua/item.lua;
         
        }

 上面创建的时候已经创建过了,所以这里不用再创建了。

 测试成功

最终的配置文件

#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;
    underscores_in_headers on;#表示如果header name中包含下划线,则不忽略

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

    server {
        listen       8081;
        server_name  localhost;
       	location  /api/item {
	    #默认的响应类型
	    default_type application/json;	
            #响应结果由lua/item.lua文件决定
	    content_by_lua_file lua/item.lua;
	}
         location / {
            root   html;
            index  index.html index.htm;
        }
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }
}

请求参数处理

 在配置文件里面修改如下

 location ~ /api/item/(\d+) 

然后修改lua文件

-- 获取路径参数
local id = ngx.var[1]
-- 返回结果
ngx.say('{"id":'..id..',"name":"SALSA AIR","title":"RIMOWA 29寸托运箱拉杆箱 SALSA AIR垃圾蛇 820.70.36.4","price":17900,"image":"https://m.360buyimg.com/mobilecms/s720x720_jfs/t6934/364/1195375010/84676/e9f2c55f/597ece38N0ddcbc77.jpg!q70.jpg.webp","category":"拉杆箱","brand":"RIMOWA","spec":"","status":1,"createTime":"2019-04-30T16:00:00.000+00:00","updateTime":"2019-04-30T16:00:00.000+00:00","stock":2999,"sold":31290}')

重新加载配置文件后 

测试成功,传过去的参数成功传递回来。

封装Http请求

这里缓存的数据都要先查询tomcat获取,然后才能保存在缓存当中。这里openResty和tomcat不在同一个地址,windows电脑地址只要把虚拟机地址的最后一位改成1就一定是windows电脑的地址.

 这里内部发送的请求会被nginx自己捕获,然后要让nginx再次反向代理到tomcat所在ip和端口.

         

查询Tomcat

前面已经封装好了一个查询工具类。

这里要修改item.lua将请求转到common.lua.

-- 导入common函数库
local common = require('common')
local read_http = common.read_http
-- 获取路径参数
local id = ngx.var[1]
-- 查询商品信息
local itemJSON = read_http("/item/"..id,nil)
--查询库存信息
local stockJSON = read_http("/item/stock/"..id,nil)
-- 返回结果
ngx.say(itemJSON)

 tmd,终于成功了.

虽然现在数据不全,接下来修改item.lua进行数据的拼接

-- 导入common函数库
local common = require('common')
local read_http = common.read_http
--导入cjson库
local cjson= require('cjson')
-- 获取路径参数
local id = ngx.var[1]
-- 查询商品信息
local itemJSON = read_http("/item/"..id,nil)
--查询库存信息
local stockJSON = 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))

然后现在库存也可以正常显示了. 

根据商品id对tomcat集群负载均衡

这里会有个问题,假如一个数据保存在8081的缓存里了,但是下一次访问到8082时就无法命中缓存。所以这里需要让同一个id每次都指向同一台tomcat。需要修改nginx的负载均衡算法。

#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;

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

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

    server {
        listen       8081;
        server_name  localhost;
        location /item {
            proxy_pass http://tomcat-cluster;
        }

        location ~ /api/item/(\d+) {
            #默认的响应类型
            default_type application/json;
            #响应结果由lua/item.lua文件决定
            content_by_lua_file lua/item.lua;
        }
         location / {
            root   html;
            index  index.html index.htm;
        }
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }
}

然后启动两台tomcat机器。

成功实现根据哈希值进行负债均很。

Redis缓存预热

 

@Component
public class RedisHandler implements InitializingBean {
    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private IItemService itemService;

    @Autowired
    private IItemStockService stockService;

    private static final ObjectMapper MAPPER=new ObjectMapper();

    @Override
    public void afterPropertiesSet() throws Exception {
        //初始化缓存
        //1.查询商品信息
        List<Item> list = itemService.list();
        //2.放入缓存
        for(Item item:list){
            //2.1item序列化为JSON
            String json = MAPPER.writeValueAsString(item);
            //2.2存入redis
            redisTemplate.opsForValue().set("item:id:"+item.getId(),json);
        }

        //3.查询商品库存信息
        List<ItemStock> stockList = stockService.list();
        //4.放入缓存
        for(ItemStock itemStock:stockList){
            //2.1item序列化为JSON
            String json = MAPPER.writeValueAsString(itemStock);
            //2.2存入redis
            redisTemplate.opsForValue().set("item:stock:id:"+itemStock.getId(),json);
        }
    }
}

 成功实现缓存预热

查询Redis缓存

 最终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 then
        ngx.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 then
        ngx.log(ngx.ERR, "连接redis失败 : ", err)
        return nil
    end
    red:auth(password)
    -- 查询redis
    local resp, err = red:get(key)
    -- 查询失败处理
    if not resp then
        ngx.log(ngx.ERR, "查询Redis失败: ", err, ", key = " , key)
    end
    --得到的数据为空处理
    if resp == ngx.null then
        resp = nil
        ngx.log(ngx.ERR, "查询Redis数据为空, key = ", key)
    end
    close_redis(red)
    return resp
end
-- 封装函数,发送http请求,并解析响应
local function read_http(path, params)
    local resp = ngx.location.capture(path, {
        method = ngx.HTTP_GET,
        args = params,
    })
    if not resp then
        -- 记录错误信息,返回404
        ngx.log(ngx.ERR, "http查询失败, path: ", path, ", args: ", args)
        ngx.exit(404)
    end
    return resp.body
end
-- 将方法导出
local _M = {
    read_http = read_http,
    read_redis = read_redis
}
return _M

-- 导入common函数库
local common = require('common')
local read_http = common.read_http
local read_redis = common.read_redis
--导入cjson库
local cjson= require('cjson')

-- 封装查询函数
function read_data(key,path,params)
    --查询redis
    local resp = read_redis("8.134.198.34",6379,key)
    --判断查询结果
    if not resp then
        ngx.log("redis查询失败,尝试去查询http,key:",key)
        --redis 查询失败,去查询http
        resp = read_http(path,params)
    end
    return resp
end

-- 获取路径参数
local id = ngx.var[1]


-- 查询商品信息
local itemJSON = read_data("item:id:"..id,"/item/"..id,nil)
--查询库存信息
local stockJSON = read_data("item:stock:id:"..id,"/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))

成功实现关了后端后从自从redis缓存查数据

Nginx本地缓存

 

# 共享字典,也就是本地缓存,名称叫做:item_cache,大小150m
lua_shared_dict item_cache 150m; 

 

 

成功item.lua代码

-- 导入common函数库
local common = require('common')
local read_http = common.read_http
local read_redis = common.read_redis
--导入cjson库
local cjson= require('cjson')
--导入共享词典,本地缓存
local item_cache = ngx.shared.item_cache

-- 封装查询函数
function read_data(key,path,params)
    -- 查询本地缓存
    local val = item_cache:get(key)
    if not val then
        ngx.log(ngx.ERR,"本地缓存查询失败,尝试去查询redis,key:",key)
        --查询redis
        val = read_redis("127.0.0.1",6379,key)
        --判断查询结果
        if not val then
            ngx.log(ngx.ERR,"redis查询失败,尝试去查询http,key:",key)
            --redis 查询失败,去查询http
            val = read_http(path,params)
        end
    end
    -- 查询成功,把数据写入本地缓存
    item_cache:set(key,val,expire)
    -- 返回数据
    return val
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))

缓存同步策略

数据同步策略

 

安装Canal

1.开启Mysql主从同步

Canal是基于MySQL的主从同步功能,因此必须先开启MySQL的主从功能才可以。

1.1开启binlog

打开mysql容器挂载的日志文件my.cnf,我的在/docker_volume/mysql/conf目录:

添加如下内容:

log-bin=/var/lib/mysql/mysql-bin
binlog-do-db=heima

配置解读:

  • log-bin=/var/lib/mysql/mysql-bin:设置binary log文件的存放地址和文件名,叫做mysql-bin

  • binlog-do-db=heima:指定对哪个database记录binary log events,这里记录heima这个库

最终文件

[mysqld]
skip-name-resolve
character_set_server=utf8
datadir=/var/lib/mysql
bind-address = 0.0.0.0
server-id=1000
log-bin=/var/lib/mysql/mysql-bin
binlog-do-db=heima
1.2设置用户权限

接下来添加一个仅用于数据同步的账户,出于安全考虑,这里仅提供对heima这个库的操作权限。

create user canal@'%' IDENTIFIED by 'canal';
GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT,SUPER ON *.* TO 'canal'@'%' identified by 'canal';
FLUSH PRIVILEGES;

重启之后测试设置是否成功:在mysql控制台,或者Navicat中,输入命令:

show master status;

2.安装Canal

我们需要创建一个网络,将MySQL、Canal、MQ放到同一个Docker网络中:

2.1创建网络
docker network create heima

让mysql加入这个网络:  

docker network connect heima mysql
2.2docker安装Canal

拉取Canal镜像

docker pull canal/canal-server:v1.1.5

创建容器

docker run -p 11111:11111 --name canal \
-e canal.destinations=heima \
-e canal.instance.master.address=mysql:3306  \
-e canal.instance.dbUsername=canal  \
-e canal.instance.dbPassword=canal  \
-e canal.instance.connectionCharset=UTF-8 \
-e canal.instance.tsdb.enable=true \
-e canal.instance.gtidon=false  \
-e canal.instance.filter.regex=heima\\..* \
--network heima \
-d canal/canal-server:v1.1.5

 配置说明

  • -p 11111:11111:这是canal的默认监听端口

  • -e canal.instance.master.address=mysql:3306:数据库地址和端口,如果不知道mysql容器地址,可以通过docker inspect 容器id来查看

  • -e canal.instance.dbUsername=canal:数据库用户名

  • -e canal.instance.dbPassword=canal :数据库密码

  • -e canal.instance.filter.regex=:要监听的表名称,上面是监听了heima库下的所有表。

表名称监听支持的语法:

mysql 数据解析关注的表,Perl正则表达式.
多个正则之间以逗号(,)分隔,转义符需要双斜杠(\\) 
常见例子:
1.  所有表:.*   or  .*\\..*
2.  canal schema下所有表: canal\\..*
3.  canal下的以canal打头的表:canal\\.canal.*
4.  canal schema下的一张表:canal.test1
5.  多个规则组合使用然后以逗号隔开:canal\\..*,mysql.test1,mysql.test2 

使用docker logs -f canal查看运行日志。 

查看canal运行日志

docker exec -it canal bash 
tail -f canal-server/logs/canal/canal.log

tail -f canal-server/logs/heima/heima.log

 

监听Canal

 

 

@Data
@TableName("tb_item")
public class Item {
    @TableId(type = IdType.AUTO)
    @Id
    private Long id;//商品id
    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)
    @Transient
    private Integer stock;
    @TableField(exist = false)
    @Transient
    private Integer sold;
}

 在redisHandler中增加两个方法

    public void saveItem(Item item)   {
        try {
            String json = MAPPER.writeValueAsString(item);
            redisTemplate.opsForValue().set("item:id:"+item.getId(),json);
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }
    }

    public void deleteItemById(Long id){
        redisTemplate.delete("item:id:"+id);
    }
@CanalTable("tb_item")
@Component
public class ItemHandler implements EntryHandler<Item> {
    @Autowired
    private RedisHandler redisHandler;
    @Autowired
    private Cache<Long,Item> itemCache;
    @Override
    public void insert(Item item) {
        //写数据到JVM缓存
        itemCache.put(item.getId(),item);
        //写数据到redis
        redisHandler.saveItem(item);
    }

    @Override
    public void update(Item before, Item after) {
        //写数据到JVM缓存
        itemCache.put(after.getId(),after);
        //写数据到redis
        redisHandler.saveItem(after);
    }

    @Override
    public void delete(Item item) {
        //删除数据到JVM缓存
        itemCache.invalidate(item.getId());
        //删除数据到redis
        redisHandler.deleteItemById(item.getId());
    }
}

测试数据监听

用已经准备好的静态资源页面

修改之后可以看见控制台输出

 到redis里面也可以看见修改后的数据

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/255438.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C++ Qt 开发:ListWidget列表框组件

Qt 是一个跨平台C图形界面开发库&#xff0c;利用Qt可以快速开发跨平台窗体应用程序&#xff0c;在Qt中我们可以通过拖拽的方式将不同组件放到指定的位置&#xff0c;实现图形化开发极大的方便了开发效率&#xff0c;本章将重点介绍ListWidget列表框组件的常用方法及灵活运用。…

【网络安全】-Linux操作系统基础

文章目录 Linux操作系统目录结构Linux命令格式Linux文件和目录操作命令Linux用户和用户组操作命令Linux查看和操作文件内容命令Linux文件压缩和解压缩命令Linux网络管理命令Linux磁盘管理和系统状态命令Linux安全加固总结 Linux是一个强大的操作系统&#xff0c;广泛用于服务器…

C# WPF上位机开发(进度条操作)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 软件上面如果一个操作比较缓慢&#xff0c;或者说需要很长的时间&#xff0c;那么这个时候最好添加一个进度条&#xff0c;提示一下当前任务的进展…

通过层进行高效学习:探索深度神经网络中的层次稀疏表示

一、介绍 深度学习中的层次稀疏表示是人工智能领域日益重要的研究领域。本文将探讨分层稀疏表示的概念、它们在深度学习中的意义、应用、挑战和未来方向。 最大限度地提高人工智能的效率和性能&#xff1a;深度学习系统中分层稀疏表示的力量。 二、理解层次稀疏表示 分层稀疏表…

【MATLAB】数据拟合第11期-基于粒子群迭代的拟合算法

有意向获取代码&#xff0c;请转文末观看代码获取方式~也可转原文链接获取~ 1 基本定义 基于粒子群迭代的拟合算法是一种优化技术&#xff0c;它基于粒子群优化算法&#xff08;PSO&#xff09;的基本思想。该算法通过群体中个体之间的协作和信息共享来寻找最优解。 在基于粒…

探索拉普拉斯算子:计算机视觉中用于边缘检测和图像分析的关键工具

一、介绍 拉普拉斯算子是 n 维欧几里得空间中的二阶微分算子&#xff0c;表示为 ∇。它是函数梯度的发散度。在图像处理的上下文中&#xff0c;该运算符应用于图像的强度函数&#xff0c;可以将其视为每个像素具有强度值的二维信号。拉普拉斯算子是计算机视觉领域的关键工具&am…

基于VUE3+Layui从头搭建通用后台管理系统(前端篇)十五:基础数据模块相关功能实现

一、本章内容 本章使用已实现的公共组件实现系统管理中的基础数据中的验证码管理、消息管理等功能。 1. 详细课程地址: 待发布 2. 源码下载地址: 待发布 二、界面预览 三、开发视频 3.1 B站视频地址: 基于VUE3+Layui从头搭建通用后台管理系统合集-验证码功能实现 3.2 西瓜…

不做数据采集,不碰行业应用,专注数字孪生PaaS平台,飞渡科技三轮融资成功秘诀

12月15日&#xff0c;飞渡科技在北京举行2023年度投资人媒体见面会&#xff0c;全面分享其产品技术理念与融资之路。北京大兴经开区党委书记、管委会主任常学智、大兴经开区副总经理梁萌、北京和聚百川投资管理有限公司&#xff08;以下简称“和聚百川”&#xff09;投资总监严…

pytorch实现DCP暗通道先验去雾算法及其onnx导出

pytorch实现DCP暗通道先验去雾算法及其onnx导出 简介实现ONNX导出导出测试 简介 最近在做图像去雾&#xff0c;于是在Pytorch上复现了一下dcp算法。暗通道先验去雾算法是大神何恺明2009年发表在CVPR上的一篇论文&#xff0c;还获得了当年的CVPR最佳论文。 实现 具体原理就不…

麻雀规则设计器maquerule

规则设计器 1、应用场景 目前市场上主要的规则引擎中都可以动态解析脚本语言&#xff0c;比如javascript,drools,aviator。这些语言解析在业务上可以两种方式&#xff0c;一种是开发人员直接把相应的脚本写好&#xff0c;跟随程序一起交付&#xff1b; 第二种就是现场的人员可…

云原生之深入解析如何在K8S环境中使用Prometheus来监控CoreDNS指标

一、什么是 Kubernetes CoreDNS&#xff1f; CoreDNS 是 Kubernetes 环境的DNS add-on 组件&#xff0c;它是在控制平面节点中运行的组件之一&#xff0c;使其正常运行和响应是 Kubernetes 集群正常运行的关键。DNS 是每个体系结构中最敏感和最重要的服务之一。应用程序、微服…

USB2.0 Spec

USB System Description A USB system is described by three definitional areas: • USB interconnect • USB devices • USB host USB interconnect The USB interconnect is the manner in which USB devices are connected to and communicate with the host. USB Ho…

Go集成elasticsearch8极简demo,光速入门

Go集成elasticsearch8极简demo,光速入门 配置go环境创件go mod工程代码实现配置go环境 编辑器添加goproxy GO111MODULE=on;GOPROXY=https://mirrors.wps.cn/go/,https://goproxy.cn,direct;GOSUMDB=off创件go mod工程 mkdir demo cd demo go mod init demo代码实现 在demo…

建行江门市分行致力数字人民币加速融入百姓生活

数字金融是“数字中国”建设的重要组成部分。建行江门市分行主动拥抱数字经济浪潮&#xff0c;全力探索数字金融赋能实体经济和社会民生&#xff0c;不断助力增强人民群众对金融服务的获得感、幸福感和安全感。 缴交住维如此简单&#xff01; 江门是著名侨乡&#xff0c;有40…

对JVM内存模型的理解

程序员的公众号&#xff1a;源1024&#xff0c;获取更多资料&#xff0c;无加密无套路&#xff01; 最近整理了一份大厂面试资料《史上最全大厂面试题》&#xff0c;Springboot、微服务、算法、数据结构、Zookeeper、Mybatis、Dubbo、linux、Kafka、Elasticsearch、数据库等等 …

服务器RAID配置及功能介绍

服务器RAID配置及功能介绍 一、RAID磁盘阵列详解1.RAID磁盘阵列介绍2.RAID 03.RAID14.RAID35.RAID56.RAID67.RAID 10总结阵列卡介绍 一、RAID磁盘阵列详解 1.RAID磁盘阵列介绍 ①是Redundant Array of lndependent Disks的缩写中文简称为独立冗余磁盘阵列。 ②把多块独立的物…

揭秘 `nextTick`:解决异步回调的利器(上)

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

两位技术领导者的故事——英特尔和高通

对于科技行业来说&#xff0c;包括这样一个现实&#xff1a;上学、工作和娱乐实际上是未来生活的一部分。科技行业也面临着变革&#xff0c;行业内发生了几起重大收购和管理层变动。其中两个最具影响力的变化是英特尔和高通的换岗。具有讽刺意味的是&#xff0c;这两家公司在过…

OA、CRM、ERP之间的区别和联系是什么?

OA、CRM、ERP之间的区别和联系是什么&#xff1f; OA、CRM、ERP&#xff0c;这些系统都是用于提高企业运营效率和管理的工具&#xff0c;它们可能在某些功能上有重叠&#xff0c;比如 CRM 和 ERP 可能都涉及到客户数据管理&#xff0c;但它们的重点和功能侧重点是不同的。 我们…

ArrayList的初始化容量与扩容机制解析

目录 1. ArrayList初始化容量 2. ArrayList的扩容机制 2.1. 计算新容量 2.2. 创建新数组并复制数据 2.3. 更新内部数组引用 3. ArrayList扩容的性能优化 3.1. 批量添加元素 3.2. 避免无效的扩容 3.3. 初始容量设定 4. 总结 在Java中&#xff0c;ArrayList是一个非常常…