Redis分布式缓存方案

news2024/11/24 19:57:15

分布式缓存

单节点Redis问题

  • 数据丢失:数据持久化
  • 并发能力弱:搭建主从集群,实现读写分离
  • 故障恢复问题:哨兵实现健康检测,自动恢复
  • 存储能力:搭建分片集群,利用插槽机制实现动态扩容

Redis持久化

RDB持久化
数据库备份文件,也叫快照,把内存数据存到磁盘。使用save进行主动RDB,会阻塞所有命令。建议使用bgsave开启子进程执行RDB。Redis停机时会被动执行一次RDB。

RDB:

bgsave开始会fork主进程得到子进程,子进程共享主进程的内存数据,完成fork后读取内存数据并写入RDB文件。
在这里插入图片描述

注:在fork时插入的数据会影响一致性,所以采用copy-on-write技术,当主进程执行读操作,刚问共享内存。当主进程执行写操作,则拷贝一份数据,执行写。

配置 :

Redis内部可在redis.conf配置RDB触发机制。

总结:

  1. RDB方式bgsave基本流程?
    -fork主进程得到子进程,共享内存空间。
    -子进程读取内存数据,并写入RDB。
    -用新RDB文件替换旧的RDB文件。
  2. RDB会在什么时候执行?save60 1000代表什么?
    -手动save或bgsave,被动服务停止时。代表60s内至少修改1000次才触发RDB。
  3. RDB缺点?
    -RDB执行间隔时间长,两次RDB之间写入数据有丢失风险。
    -fork子进程,压缩,写出RDB文件都比较耗时。

AOF:

AOF持久化
追加文件。redis每一条写命令都记录在AOF文件,是命令日志文件。

配置:

Redis的AOF默认关闭,需修改redis.conf的appendonly来开启AOF。可配置appendfsync改变刷盘策略的记录频率:always,everysec(默认),no(由操作系统决定)。

  • always 同步刷盘 可靠性高,几乎不丢失数据 性能差
  • everysec 每秒刷盘 性能中 最多丢失1s数据
  • no 操作系统控制 性能好 可靠性差,丢失数据大

auto-aof-rewrite-percentage 100 文件比上次增长超过几%则触发重写
auto-aof-rewrite-min-size 64mb 文件多大重写

问题:

AOF会比RDB大很多,而且对同一个KEY多次写操作,只有最后一次才有意义,通过执行bgrewriteaof命令,可以让AOF文件执行重写。

RDB和AOF对比

如果对数据安全性要求高,则需结合两者使用
<图:RDB和AOF对比 aof&rdb.jpg>

Redis主从集群

搭建主从架构

单节点Reds并发不高,就需要主从集群,实现读写分离。

准备

要在同一台虚拟机开启3个实例,必须准备三份不同的配置文件和目录。开启RDB关闭AOF。拷贝redis.conf到三个目录。

问题:为什么Redis是主从集群而不是负载均衡集群?

Redis读多写少,主节点做写操作,从节点做读操作。

开启主从关系

使用replicaof或者slaveof命令,有临时和永久两种模式。

数据同步原理

主从第一次同步是全量同步,第二次是同步记录rdb期间的所有命令,第三次发送repl_baklog中的命令。
《图:数据同步原理》

问题:master如何判断slave是不是第一次来同步数据?

  • replication Id:简称replid,是数据集的标记,id一致则说明是同一数据集,每一次master都有唯一的replid,slave会继承master节点的replid。
  • offset:偏移量,如果slave的offset小于master的offset,说明lave的数据落后于master,需要更新。
    因此salve做数据同步,必须想master声明replicationId和offset,master才能判断传输的数据。

全量同步流程

master将完整内存数据生成rdb,发送给slave,后续命令则记录在repl_baklog,逐个发给slave
注:slave节点第一次链接master时进行全量同步,或者slave断开太久repl_baklog中的offset已被覆盖

  1. slave节点请求增量同步
  2. master节点判断replid,不一致则拒绝增量同步
  3. master将完整的内存数据生成rdb,发送到slave
  4. slave清空本地数据,加载master的rdb
  5. master将rdb期间的命令记录在repl_baklog中,并持续将log中的命令发送slave。

增量同步

slave将自己的offset提交到master,master获取repl_baklog中从offset之后的命令给slave
注:slave节点断开又恢复,并在repl_baklog中找到offset时
《图:增量同步》

优化Redis主从集群

  1. 在master中配置repl-diskless-sync yes启用无磁盘复制,避免全量同步时的磁盘IO
  2. Redis单节点上的内存占用不要太大,减少RDB导致的磁盘IO
  3. 适当提高repl_baklog的大小,发展slave宕机尽快回复,避免全量
  4. 限制一个master上的slave数量,太多salve建议采用主-从-从链式结构,减少master压力

问题:salve宕机可以找master同步数据,master宕机怎们办呢?

哨兵机制-主从切换

Redis哨兵

哨兵作用和原理 重要

Redis哨兵(Sentinel)机制实现主从集群的自动故障恢复。
《图:Redis哨兵》

  • 监控:Sentinel会不断检查master和slave是否按预期工作。
  • 故障恢复:master故障哨兵会将一个新的slave提升为master。当故障实例恢复后也以新的master为主。
  • 通知:Sentinel充当Redis客户端服务发现来源,当集群发生故障转移时,会将最新消息推送给Redis的客户端。

服务状态监控
Sentinel基于心跳机制检测服务状态,每1s向集群的每个实例发送ping命令:

  1. 主观下线:如果sentinel节点发现某个实例未在规定时间响应,则认为该实例主动下线。
  2. 客观下线:若超过指定数量(quorum)的sentinel都认为该实例主观下线,则该实例客观下线,quorum值最好超过sentinel实例的一半。

选举新的master规则:

  • 首先判断slave节点与master节点断开时间长短,如果超过指定值(down-after-milliseconds*10)则会排除该slave几点
  • 判断slave节点的slave-priority值,越小优先级越高,为0则永不参与选举。
  • 如slave-priority一样,则判断slave节点的offset值,越大说明值越新,优先级越高。
  • 最后是判断slave节点的运行id大小,越小优先级越高。

如何实现故障转移(当选择slave1为新master后):

  1. sentinel给备选的slave1发送slaveofnoone(别当奴隶了)命令,让该节点成为新的master。
  2. sentinel给其他slave发送slaveof slave1的ip+端口号,让他们成为slave1的从节点,从新的master上同步数据。
  3. sentinel将故障节点标记为slave发送slaveod命令,当故障节点恢复后会自动成为新的master的slave节点。

搭建哨兵集群

具体搭建流程参考百度:《Redis集群.md》

RedisTemplate的哨兵模式

spring的RedisTemplate底层利用lettuce实现节点的感知和自动切换。

  1. pom:spring-boot-starter-data-redis
  2. 配置sentinel信息
  3. 启动类配置主从读写分离
/**
 * MASTER:从主节点读取
 * MASTER_PREFERRED:优先master读取,不可用读取replica
 * REPLICA:从slave读取
 * ERPLICA_PREFERRED:优先从slave节点读取,所有的slave都不可用才读取master
 */
@Bean
public LettuceClientConfigurationBuilderCustomizer configurarionBuilderCustomizer(){
    return configBuilder -> configBuilder.readFrom(ReadFrom.ERPLICA_PREFERRED);
}

Redis分片集群

为什么需要分片集群?
主从和哨兵可以解决高可用,高并发读问题,但依然还有两个问题没有解决:

  • 海量数据存储问题
  • 高并发写问题

使用分片集群可以解决上述问题,分片集群特征:

  • 集群中有多个master,每个master保存不同的数据。
  • 每个master都可以有多个slave节点。
  • master之间通过ping检测彼此之间健康状态。
  • 客户端请求可以访问任何集群任意节点,最终都会被转发到正确的节点。
    《图: Redis分片集群》

搭建分片集群

具体搭建流程参考百度:《Redis分片集群.md》

散列插槽

Redis会把每一个master节点映射到0~16383个插槽(hash slot)上去,查看集群信息时就能看到。
数据的key不是与节点绑定,而是与插槽绑定。redis会根据key的有效部分计算插槽值,计算方式为CRC16算法得到一个hash值,然后对16384取余得到slot值,分两种情况:

  • key中包含{},且{}中至少包含1个字符,{}中的部分是有效部分(key是{test}num,则根据test计算)
  • key中不包含{},整个key都是有效部分。(key是num,则根据num计算)

问题:为什么数据要绑定插槽上呢?
数据跟着插槽走,宕机时对应插槽可以转移到活着的节点。
问题:如何将同一类的数据固定的保存在同一个redis实例?
使用相同{key}。

集群伸缩

添加一个节点到集群:redis -cli --cluster add -node
需求:向集群中添加一个新的master节点,并向其中存储num=10

  1. 启动一个新的redis实例,指定端口:redis-server port/redis.conf
  2. 添加实例端口到之前的集群,作为一个master节点:redis-cli --cluster add-node ip:port ip:port(已存在的)
  3. 给实例端口节点分配插槽,使得num这个key可以存储到实例端口(难点):redis-cli --cluster reshard ip:port(已存在的) num(插槽数量) 然后根据提示配置好插槽即可。
    练习:删除集群中的一个节点。

故障转移

当集群中有一个master宕机后会发生什么事情?(自动)

  1. 首先该实例与其他实例失去连接
  2. 然后疑似宕机
  3. 最后确定下线,自动提升一个slave为新的master。

数据转移(手动)
利用cluster failover命令可以手动让集群中的某个master宕机,切换到cluster failover命令的这个slave节点,实现无感知的数据迁移。《图:分片集群手动数据迁移》
Failover三种模式:缺省,force,takeover

案例:在7002这个slave节点执行手动故障转移,重回master低位:

  1. 利用redis-cli连接7002
  2. 执行cluster failover命令

RedisTemplate访问分片集群

RedisTemplate底层同样基于lettuce实现了分片集群的支持而使用的步骤和哨兵模式基本一致:

  1. 引入redis的starter依赖
  2. 配置分片集群地址
  3. 配置读写分离

多级缓存-亿级流量的缓存方案

传统缓存的问题:传统缓存策略一般是请求到tomcat后,先查询redis,如果未命中则查询数据库。

  • 请求要经过tomcat处理,tomcat性能不如redis成为整个系统的瓶颈
  • redis缓存失效,对数据库产生冲击。

多级缓存方案:利用处理请求的每个环节,分别添加缓存,减轻Tomcat压力。
用户–浏览器端缓存(静态资源渲染:检验码304,90%请求可拦截)–nginx(反向代理)–nginx本地缓存(集群)–nginx端redis缓存–tomcat进程缓存–mysql

JVM进程缓存(tomcat进程缓存)

缓存分为两类:

  • 分布式缓存(Redis):优点:量大 ,可靠性高,可以集群。缺点:有网络开销。场景:数据量大,可靠性高,集群间共享。
  • 本地进程缓存(Caffeine,HashMap,GuavaCache):优点:本地,无网络,速度快。缺点:量小,可靠性低,无法共享。场景:性能要求高,数据量小

初识Caffeine:java8开发Spring内部缓存使用(本地缓存中最优)。Cache<String, String> cache = Caffeine.newBuilder().build();利用工厂模式构造cache对象。
Caffine缓存驱逐策略:基于容量,基于时间,基于引用(性能差,不建议)。在一次读写操作后或空闲时间完成数据驱逐。

Lua语法入门(nginx+Lua)

Lua:标准C编写的脚本语言,用于嵌入应用程序中,为应用程序提供灵活的扩展和定制功能。
Lua数据结构:nil,boolean,number,string,function(c或Lua编写的函数),table(hashmap)。可用type函数返回变量类型。
Lua定义变量:local str = ‘test’,local arr = {‘java’,‘py’},local map = {name = ‘Jack’,age = 32}
Lua获取变量:arr1,map[‘name’],map.name
Lua遍历数组:for index,value in ipairs(arr) do //todo end
Lua遍历table:for key,value in pairs(map) do //todo end
Lua函数:function 函数名(arg1,arg2…) //函数体 return nil end
Lua条件控制:类似javaif,else

实现多级缓存

OpenResty:基于Nginx的高性能web平台。用于搭建处理高并发,扩展性高的动态web应用,web服务和动态网关。功能:

  • 具备Nginx的完整功能
  • 基于Lua语言进行扩展,集成了大量精良的Lua库,第三方模块
  • 允许使用Lua自定义业务逻辑,自定义库

OpenResty构思:前端请求被nginx反向代理到虚拟机OpenResty集群,请求在OpenResty中接收这个请求,并返回数据。

OpenResty流程:

  1. 修改nginx.conf文件:在http下面添加对OpenResty的Lua模块的加载;在server下面添加对path路径的监听(响应类型和响应数据)。
  2. 编写item.lua文件
    OpenResty获取请求参数:《图:OpenResty获取请求参数》

nginx向tomcat获取数据(暂不加redis缓存)

nginx内部发送Http请求:

local resp = ngx.location.capture("/path",{
    method = ngx.HTTP_GET,  --请求方式 
    args = {a=1,b=2},  --get方式传参
    body = "c=3&d=4"  -- post方式传参
})
//注意:这里的/path是路径,并不包含ip和端口。这个请求会被nginx内部的server监听并处理
暂时不经过redis缓存,直接向tomcat服务器访问获取数据,还需一个server对这个路径做反向代理:
location /path {
    #这里是windows电脑的ip和java服务端口,需确保windows防火墙处关闭状态
    proxy_pass http://192.168.150.1:8081
}

返回响应内容:resp.status(状态码),resp.header(响应头,table),resp.body(响应数据)
封装http查询的函数:我们可以把http查询请求封装为一个函数,放到openresty函数库中,方便后面使用。

  1. 在/usr/local/openresty/lualib目录下创建common.lua文件
  2. 在common.lua中封装http查询函数
--封装函数,发送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
--将方法导出为table
local _M = {
    read_http = read_http
    read_redis = read_redis //预留:后面需要加载redis模块
}
return _M

使用http查询函数:

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

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

  • 引入json模块: local cjson = require “cjson”
  • 序列化:local obj = {name=‘jack’,age = 21} local json = cjson.encode(obj)
  • 反序列化:local json = ‘{“name”: “jack”, “age”,21}’ -反序列化 local obj = cjson.decode(json) print(obj.name)

tomcat端做负载均衡:
每个tomcat的实例缓存都在本地,需要做hash映射。《图:tomcat集群负载均衡》

upstream tomcat-clusrer{
    hash $request_uri;  --基于requestUri做hash映射
    server ip:port1
    server ip:port2
}
server{
    listen  8081;
    server_name = loclahost;
    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
    }
}

nginx加redis缓存

添加Redis缓存(冷启动与缓存预热):

  • 冷启动:启动服务时Redis没有缓存,如果第一次加载全量数据缓存,会给数据库带来压力
  • 缓存预热:利用大数据统计用户访问热点数据,在启动时就将这些数据加载到Redis中
@Component
public class RedisHandler implements InitializingBean {
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired
    private Service service;
    private static final ObjectMapper MAPPER = new ObjectMapper(); //json序列化
    @Override
    public void afterPropertiesSet() throws Exception{
        //初始化缓存
        //1.查询信息
        List<Bean> list = service.list();
        //2.放入缓存
        for(Bean bean : list){
            String json = MAPPER.writeValueAsString(bean);
            redisTemplate.opsForValue.set("KEY",json);
        }
    }
}

OpenResty加载Redis模块,放入common模块,并在lcoal _M{}中将read_redis方法暴露出去:

  • 引起如Redis模块,初始化Reids对象:
local redis = require('resty.redis')
local red = redis::new()
red:set_timeout(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连接池失败:', e)
    end
end
  • 封装函数,从Redis读取数据并返回
local function read_redis(ip, port, key)
    local ok,err = red:connect(ip,port)  --获取一个连接
    if not ok then 
        ngx.log(ngx.ERR, "连接Redis失败:", e)
        return nil
    end
    //查
    lcoal resp,err = red.get(key)
    if not resp then
        ngx.log(ngx.ERR, "查询Redis失败:", e)
    end
    if resp == ngx.null then 
        resp = nil;
        ngx.log(ngx.ERR, "查询Redis为空:", e)
    end
    closr_redis(red)
    return resp
end
  • 封装函数,查询加载Redis
local function read_data(key, path, params)
    localresp = read_redis("127.0.0.1", 6379, key)
    if not resp then 
        resp = read_http(path, params)
    end
    return resp
end

nginx本地缓存

OpenResty为Nginx提供了shard dict功能,可在nginx的多个worker间共享数据,实现缓存功能。

  • 开启共享字典:在nginx.conf的http下添加配置:lua_shared_dict item_cache 150m;
  • 操作共享字典:
    local item_cache = ngx.shared.item_cache
    item_cache:set(‘key’,‘value’,1000)
    local var = item_cache:get(‘key’)

需求:1.修改item.lua中的read_data函数,优先查询本地缓存,未命中再查redis,tomcat。2.查redis或tomcat写入本地设置有效期。

缓存同步策略

  1. 设置有效期:给缓存设置有效期,到期自动查询,再次查询时更新。(差,缓存不一致,更新频率低)
  2. 同步双写:修改数据库同时修改缓存。(强,一致,有代码入侵,耦合度高)
  3. 异步通知:修改数据库时发送事件通知,相关服务监听到通知后修改缓存数据。(一般,中间状态不一致,可通知多个服务)

基于canal的异步通知

canal基于数据库增量日志解析,提供增量数据订阅&消费。可监听mysql数据库binlog通知缓存服务,更新redis,代码侵入低。
canal基于mysql的主从同步实现。

  1. mysql master将数据变更写入二进制日志(binary log),其中数据为binary log events
  2. mysql slave将master的binary log events拷贝到他的中继日志(relay log)
  3. mysql slave重放relay log中事件,将数据同步。

canal就是把自己伪装成mysql的一个slave节点,从而监听master的binlog变化。
开启canal需要修改mysql主从。

Canal客户端

坐标:<artifactId>canal-spring-boot-starter</artifactId>
配置:
canal:
    destination: xuy
    server: ip:port

编写监听类,监听canal:
canal推送给canal-client的是被修改的这一行数据,其会把行数据转换成Item实体类。需要在字段上加@TableName,@Id,@Column,@TableField(exist=false)+@Transient(非数据库字段)等注解

/**
 * 监听增,改,删的消息
*/
@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) {//新增数据到redis}
    @Override
    public void update(Item before, Item after) {//更新reids,本地缓存}
    @Override
    public void delete(Item item) {//删除reids,本地缓存}
}

多级缓存架构图

《图:多级缓存sum》

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Windows逆向安全(一)之基础知识(十八)

指针作为参数 代码 #include "stdafx.h" void function(int num){num52; } void function2(int* num){*num52; } int main(int argc, char* argv[]) {int num610;function(num);printf("%d\n",num);function2(&num);printf("%d\n",num);re…

KVM NAT 模型

目录 NAT原理 virbr0-nic作用 关于kvm中的网桥和virbr0-nic网卡需注意以下几点&#xff1a; NAT原理 virbr0-nic作用 在kvm中每次通过kvm创建一个网桥都会自动在该网桥下创建两个接口&#xff0c;与网桥同名的virbr0接口代表这个虚拟网桥&#xff0c;可以通过它来配置网桥…

vue2的生命周期

生命周期就是记录数据的状态。对数据进行操作 刚开始 new Vue() 创建了一个实例对象 beforeCreate() 数据还没有创建出来 created() 数据创建出来了&#xff0c;可以访问 判断有没有el 或 template 后 将模板编译成渲染函数 beforeMount() 数据还没有挂在到页面上面 mou…

引入tiff.js报错

当我们安装引入tiff.js时&#xff0c;会遇到这些问题&#xff0c;相关命令以及错误&#xff1a; 1、安装tiff依赖 npm install tiff.js 2、引入tiff import Tiff from tiff.js 3、错误展示&#xff1a; 这个错误是因为没有在vue.config.js以及webpack.base.conf.js(在build文…

C4D的GPU渲染器Octane和Redshift的渲染对比

对CG圈创作人员来说&#xff0c;除制作软件外渲染器是平时接触最多的一类软件&#xff0c;用渲染器进行渲染的过程&#xff0c;就是把制作软件里的预览效果变到融合材质、光照、物理特性的最终效果的这个过程&#xff0c;这是CG制作中最重要的一步&#xff0c;关乎着最终效果的…

头歌--shell脚本入门 变量、字符串--第3关:shell 字符串

任务描述 本关带领大家熟悉 shell 的变量并掌握其使用。 相关知识 字符串概念 字符串是 shell 编程中最常用最有用的数据类型&#xff08;除了数字和字符串&#xff0c;也没啥其它类型好用了&#xff09;&#xff0c;字符串可以用单引号&#xff0c;也可以用双引号&#xff…

数据库系统概论--期末复习

目录 一.绪论 一.数据库系统概述 二.数据模型 三.数据库系统的结构 四.数据库系统的组成 二.关系数据库 一.关系数据结构及形式化定义 二.关系操作 三.关系的完整性 四.关系代数 三.SQL语言 一.数据定义 二.数据查询 三.数据更新 四.视图 四.数据库安全性 1.不安…

Access Token 访问令牌 如何获取?

一、引用 三方库导入时&#xff0c;通常需要输入账号和令牌进行鉴权。账号为指定平台的 HTTP 克隆账号&#xff0c;访问令牌即 Access Token&#xff0c;本文介绍如何获取常见三方代码平台的Access Token。 Access Token 通常在代码平台的个人账号设置内进行管理和配置&#xf…

给konva加个刻度尺

给konva加个刻度尺 最近在用konva做一些&#xff0c;一开始写了不少辅助函数。帮助自己给物体定位 &#xff0c;现在贡献出来给大家用。 给图层增加刻度尺 顾名思义就是加个刻度显示&#xff0c;效果如下&#xff1a; 代码&#xff1a; 第一个参数时layer&#xff0c;第二个…

居民配电所远程监控解决方案

一、项目背景 随着城市建设提速发展、能源利用日益提高、环保节能成为了城市发展的新趋势&#xff0c;配电站逐渐成为企业和居民生活中不可或缺的组成部分。居民的生活用电需求也日益增大。如果没有及时处理好用电安全很容易出现电力中断等情况发生。因此及时高效地为用电客户…

区域LIS源码,基于云计算B/S架构医学实验室检验系统源码

基于B/S架构的医学实验室检验系统源码&#xff0c;整个系统的运行基于WEB层面&#xff0c;只需要在对应的工作台安装一个浏览器软件有外网即可访问。全套系统采用云部署模式&#xff0c;部署一套可支持多家医院检验科共同使用。 采用.Net Core新的技术框架、DEV报表、前端js封…

CASS方格网法土石方量计算

1、打开软件&#xff0c;在“绘图处理”-“展野外测点点号”&#xff0c;默认比例尺为1:500&#xff0c;如下&#xff1a; 2、打开后&#xff0c;在命令行输入pl&#xff0c;绘制范围线&#xff0c;将所有点大致圈起来&#xff0c;如下&#xff1a; 展点结果 范围线绘制结果 3…

【软件测试面试】性能测试常问面试题?不备这些真不敢去面了...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 性能测试的应用领…

给图片加颜色边框怎么弄,3种好用方法

给图片加颜色边框怎么弄&#xff1f;图片是我们每个人平时都必不可少的文件之一&#xff0c;尤其是在工作中的使用&#xff0c;不同的使用场景对于图片的要求也是不一样的&#xff0c;这就要求我们具有一定的图片处理技能&#xff0c;现在任何事情都离不开电脑&#xff0c;所以…

操作系统原理 —— 进程状态切换具体做了哪些事情?(八)

什么是进程控制&#xff1f; 进程控制的主要功能是&#xff1a;对系统中的所有进程实施有效的管理&#xff0c;它具有创建新进程、撤销已有进程、实现进程状态转换等功能。 简单来说&#xff0c;就是进程控制就是要现实进程状态的转换。 那如何实现进程的控制呢&#xff1f;…

案例01-tlias智能学习辅助系统02-文件上传

4.6 文件上传 若要实现下方的文件上传页面&#xff0c;需要前后端做如下操作&#xff1a; 后端&#xff1a; 本地存储 云存储 阿里云官方文档说明 具体实现参考上方连接&#xff0c;不再详细说明 阿里云OSS快速入门请参考文档&#xff1a; AliOSS.md &#xff08;在talis文…

不同场景下的并发容器选择

在并发编程中&#xff0c;我们经常会使用容器来存储数据或对象&#xff0c;可以依据场景的变化选择多种容器。 Map并发容器 因为在 JDK1.7 之前&#xff0c;在并发场景下使用 HashMap 会出现死循环&#xff0c;从而导致 CPU 使用率居高不下&#xff0c;而扩容是导致死循环的主…

扫描电镜学习手册

SEM测试&#xff0c;在通俗意义上来讲&#xff0c;可以看成是一个放大倍数超级大的“放大镜”。 作为当今十分有用的科学研究仪器&#xff0c;扫描电子显微镜是介于透射电镜和光学显微镜之间的一种微观性貌观察手段&#xff0c;可直接利用样品表面材料的物质性能进行微观成像。…

eureka注册中心和RestTemplate

eureka注册中心和restTemplate的使用说明 eureka的作用 消费者该如何获取服务提供者的具体信息 1.服务者启动时向eureka注册自己的信息 2.eureka保存这些信息 3.消费者根据服务名称向eureka拉去提供者的信息 如果有多个服务提供者&#xff0c;消费者该如何选择&#xff1f; 服…

基于JavaSpringMVC+Mybatis+Jquery高校毕业设计管理系统设计和实现

基于JavaSpringMVCMybatisJquery高校毕业设计管理系统设计和实现 博主介绍&#xff1a;5年java开发经验&#xff0c;专注Java开发、定制、远程、指导等,csdn特邀作者、专注于Java技术领域 作者主页 超级帅帅吴 Java项目精品实战案例《500套》 欢迎点赞 收藏 ⭐留言 文末获取源码…