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策略,最近最少使用的。

    /*基于大小设置驱逐策略:*/@Testvoid 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"));}

    /*基于时间设置驱逐策略:*/@Testvoid 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 {@Beanpublic Cache<Long, Item> itemCache(){return Caffeine.newBuilder().initialCapacity(100).maximumSize(10_000).build();}@Beanpublic Cache<Long, ItemStock> stockCache(){return Caffeine.newBuilder().initialCapacity(100).maximumSize(10_000).build();}
}

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

    @Autowiredprivate Cache<Long,Item> itemCache;@Autowiredprivate 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 {@Autowiredprivate StringRedisTemplate redisTemplate;@Autowiredprivate IItemService itemService;@Autowiredprivate IItemStockService stockService;private static final ObjectMapper MAPPER=new ObjectMapper();@Overridepublic void afterPropertiesSet() throws Exception {//初始化缓存//1.查询商品信息List<Item> list = itemService.list();//2.放入缓存for(Item item:list){//2.1item序列化为JSONString json = MAPPER.writeValueAsString(item);//2.2存入redisredisTemplate.opsForValue().set("item:id:"+item.getId(),json);}//3.查询商品库存信息List<ItemStock> stockList = stockService.list();//4.放入缓存for(ItemStock itemStock:stockList){//2.1item序列化为JSONString json = MAPPER.writeValueAsString(itemStock);//2.2存入redisredisTemplate.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 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 nilendred:auth(password)-- 查询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请求,并解析响应
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

-- 导入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)--查询redislocal resp = read_redis("8.134.198.34",6379,key)--判断查询结果if not resp thenngx.log("redis查询失败,尝试去查询http,key:",key)--redis 查询失败,去查询httpresp = read_http(path,params)endreturn 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 thenngx.log(ngx.ERR,"本地缓存查询失败,尝试去查询redis,key:",key)--查询redisval = read_redis("127.0.0.1",6379,key)--判断查询结果if not val thenngx.log(ngx.ERR,"redis查询失败,尝试去查询http,key:",key)--redis 查询失败,去查询httpval = read_http(path,params)endend-- 查询成功,把数据写入本地缓存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.logtail -f canal-server/logs/heima/heima.log

 

监听Canal

 

 

@Data
@TableName("tb_item")
public class Item {@TableId(type = IdType.AUTO)@Idprivate Long id;//商品idprivate 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;
}

 在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> {@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());}
}

测试数据监听

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

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

任务十六:主备备份型防火墙双机热备

目录 目的 器材 拓扑 步骤 一、基本配置 配置各路由器接口的IP地址【省略】 1、配置BGP协议实现Internet路由器之间互联 2、防火墙FW1和FW2接口IP配置与区域划分 3、配置区域间转发策略 4、配置NAPT和默认路由 5、配置VRRP组&#xff0c;并加入Active/standby VGMP管…

图片转excel:二种合并方式,有何区别?

图片怎么转为可编辑的excel&#xff0c;并且将转换结果合并为一个表&#xff1f;打开眼精星表格文字识别电脑客户端&#xff0c;我们可以看到顶部有一个功能&#xff0c;名为“表格合并”&#xff0c;而在表格识别模块提交选项里&#xff0c;我们会发现有“合并”选项&#xff…

什么是关键词排名蚂蚁SEO

关键词排名是指通过搜索引擎优化&#xff08;SEO&#xff09;技术&#xff0c;将特定的关键词与网站相关联&#xff0c;从而提高网站在搜索引擎中的排名。关键词排名对于网站的流量和用户转化率具有至关重要的影响&#xff0c;因此它是SEO工作中最核心的部分之一。 如何联系蚂…

C# WPF上位机开发(usb设备访问)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 目前很多嵌入式设备都支持usb访问&#xff0c;特别是很多mcu都支持高速usb访问。和232、485下个比较&#xff0c;usb的访问速度和它们基本不在一个…

微软近日推出了Phi-2,这是一款小型语言模型,但其性能却十分强大

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

redis各种数据类型的应用场景

String应用场景 单值缓存 SET key value GET key 对象缓存 SET user:1 value(json格式数据)MSET user:1:name zhuge user:1:balance 1888 MGET user:1:name user:1:balance 分布式锁 SETNX product:10001 true //返回1代表获取锁成功 …

iPhone 17Pro/Max或升级4800万像素长焦镜头,配备自研Wi-Fi 7芯片。

iPhone 16未至&#xff0c;关于iPhone 17系列的相关消息就已经放出&#xff0c;到底是谁走漏了风声。 海通国际证券技术分析师Jeff Pu近日发布报告称&#xff0c;苹果将为2025年推出的iPhone 17ProMax配备4800万像素的长焦镜头。经调查&#xff0c;该分析师认为提升iPhone拍摄方…

【百度PARL】强化学习笔记

文章目录 强化学习基本知识一些框架Value-based的方法Q表格举个例子 强化的概念TD更新 Sarsa算法SampleSarsa Agent类 On_policy vs off_policy函数逼近与神经网络DQN算法DQN创新点DQN代码实现model.pyalgorithm.pyagent.py总结&#xff1a;举个例子 实战 视频&#xff1a;世界…

centos安装opencv并在springboot中使用

使用conda安装opencv&#xff0c;并在docker运行的容器中使用&#xff0c;这里以运行则springboot应用的容器为例 步骤一&#xff1a;安装 在conda中安装 # 安装依赖 conda install numpy matplotlib# 安装opencv conda install -c conda-forge opencv # 或者制定版本 conda…

AI创作系统ChatGPT网站源码,支持AI绘画,支持GPT语音对话+智能思维导图生成

一、前言 SparkAi创作系统是基于ChatGPT进行开发的Ai智能问答系统和Midjourney绘画系统&#xff0c;支持OpenAI-GPT全模型国内AI全模型。本期针对源码系统整体测试下来非常完美&#xff0c;可以说SparkAi是目前国内一款的ChatGPT对接OpenAI软件系统。那么如何搭建部署AI创作Ch…

开发企业展示小程序的关键步骤和技巧

随着移动互联网的快速发展&#xff0c;小程序已经成为企业展示形象、推广产品和服务的重要工具。拥有一个优秀的小程序可以帮助企业提高品牌知名度&#xff0c;吸引更多潜在客户&#xff0c;提升用户体验。以下是拥有一个展示小程序的步骤&#xff1a; 确定需求和目标 首先&am…

linux修改用户uid和gid并且修改文件所有权(所属用户及所属用户组)(chown命令、chgrp命令)(批量修改查找并修改文件、目录uid和gid)

文章目录 修改Linux用户UID和GID以及文件所有权1. 修改用户的UID和GID1.1 用户UID和GID的概念1.2 修改用户UID1.3 修改用户GID 2. 修改文件所有权2.1 文件所有权的概念2.2 修改文件所有者&#xff08;chown命令&#xff09;2.3 修改文件所属用户组&#xff08;chgrp命令&#x…