redis高级案列case

案列一 双写一致性

案例二 双锁策略

package com.redis.redis01.service;import com.redis.redis01.bean.RedisBs;
import com.redis.redis01.mapper.RedisBsMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;import javax.annotation.Resource;
import java.beans.Transient;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;@Slf4j
@Service
public class RedisBsService {//定义key前缀/命名空间public static final String CACHE_KEY_USER = "user:";@Autowiredprivate RedisBsMapper mapper;@Resourceprivate RedisTemplate<String, Object> redisTemplate;private static ReentrantLock lock = new ReentrantLock();/*** 业务逻辑没有写错,对于中小长(qps<=1000)可以使用,但是大厂不行:大长需要采用双检加锁策略** @param id* @return*/@Transactionalpublic RedisBs findUserById(Integer id,int type,int qps) {//qps<=1000if(qps<=1000){return qpsSmall1000(id);}//qps>1000return qpsBig1000(id, type);}/*** 加强补充,避免突然key失效了,或者不存在的key穿透redis打爆mysql,做一下预防,尽量不出现缓存击穿的情况,进行排队等候* @param id* @param type 0使用synchronized重锁,1ReentrantLock轻量锁* @return*/private RedisBs qpsBig1000(Integer id, int type) {RedisBs redisBs = null;String key = CACHE_KEY_USER + id;//1先从redis里面查询,如果有直接返回,没有再去查mysqlredisBs = (RedisBs) redisTemplate.opsForValue().get(key);if (null == redisBs) {switch (type) {case 0://加锁,假设请求量很大,缓存过期,大厂用,对于高qps的优化,进行加锁保证一个请求操作,让外面的redis等待一下,避免击穿mysqlsynchronized (RedisBsService.class) {//第二次查询缓存目的防止加锁之前刚好被其他线程缓存了redisBs = (RedisBs) redisTemplate.opsForValue().get(key);if (null != redisBs) {//查询到数据直接返回return redisBs;} else {//数据缓存//查询mysql,回写到redis中redisBs = mapper.findUserById(id);if (null == redisBs) {// 3 redis+mysql都没有数据,防止多次穿透(redis为防弹衣,mysql为人,穿透直接伤人,就是直接访问mysql),优化:记录这个null值的key,列入黑名单或者记录或者异常return new RedisBs(-1, "当前值已经列入黑名单");}//4 mysql有,回写保证数据一致性//setifabsentredisTemplate.opsForValue().setIfAbsent(key, redisBs,7l, TimeUnit.DAYS);}}break;case 1://加锁,大厂用,对于高qps的优化,进行加锁保证一个请求操作,让外面的redis等待一下,避免击穿mysqllock.lock();try {//第二次查询缓存目的防止加锁之前刚好被其他线程缓存了redisBs = (RedisBs) redisTemplate.opsForValue().get(key);if (null != redisBs) {//查询到数据直接返回return redisBs;} else {//数据缓存//查询mysql,回写到redis中redisBs = mapper.findUserById(id);if (null == redisBs) {// 3 redis+mysql都没有数据,防止多次穿透(redis为防弹衣,mysql为人,穿透直接伤人,就是直接访问mysql),优化:记录这个null值的key,列入黑名单或者记录或者异常return new RedisBs(-1, "当前值已经列入黑名单");}//4 mysql有,回写保证数据一致性redisTemplate.opsForValue().set(key, redisBs);}} catch (Exception e) {e.printStackTrace();} finally {//解锁lock.unlock();}}}return redisBs;}private RedisBs qpsSmall1000(Integer id) {RedisBs redisBs = null;String key = CACHE_KEY_USER + id;//1先从redis里面查询,如果有直接返回,没有再去查mysqlredisBs = (RedisBs) redisTemplate.opsForValue().get(key);if (null == redisBs) {//2查询mysql,回写到redis中redisBs = mapper.findUserById(id);if (null == redisBs) {// 3 redis+mysql都没有数据,防止多次穿透(redis为防弹衣,mysql为人,穿透直接伤人,就是直接访问mysql),优化:记录这个null值的key,列入黑名单或者记录或者异常return new RedisBs(-1, "当前值已经列入黑名单");}//4 mysql有,回写保证数据一致性redisTemplate.opsForValue().set(key, redisBs);}return redisBs;}}

案列三 mysql+redis实时同步

下载canal监控端admin和服务端deployer

https://github.com/alibaba/canal/releases/tag/canal-1.1.7

image.png

登录mysql授权canal连接mysql账户

DROP USER IF EXISTS 'canal'@'%';
CREATE USER 'canal'@'%' IDENTIFIED BY 'canal';  
GRANT ALL PRIVILEGES ON *.* TO 'canal'@'%' IDENTIFIED BY 'canal';  
FLUSH PRIVILEGES;

image.png

配置canal

修改mysql ip
image.png
启动

./startup.bat

image.png

Canal客户端(Java编写)

非springboot项目

<dependency><groupId>com.alibaba.otter</groupId><artifactId>canal.client</artifactId><version>1.1.0</version>
</dependency>
server.port=8002
#连接数据源
spring.datasource.druid.username=root
spring.datasource.druid.password=xgm@2023..
spring.datasource.druid.url=jdbc:mysql://172.16.204.51:3306/redis?serverTimezone=GMT%2B8
spring.datasource.druid.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.druid.initial-size=5##指定缓存类型redis
#spring.cache.type=redis
##一个小时,以毫秒为单位
#spring.cache.redis.time-to-live=3600000
##给缓存的建都起一个前缀。  如果指定了前缀就用我们指定的,如果没有就默认使用缓存的名字作为前缀,一般不指定
#spring.cache.redis.key-prefix=CACHE_
##指定是否使用前缀
#spring.cache.redis.use-key-prefix=true
##是否缓存空值,防止缓存穿透
#spring.cache.redis.cache-null-values=true#redis
spring.redis.host=172.16.204.51
spring.redis.port=6379
spring.redis.password=123456
spring.redis.database=1# mybatis配置
mybatis:
check-config-location: true
#  mybatis框架配置文件,对mybatis的生命周期起作用
config-location: "classpath:mybatis/mybatis-config.xml"
#  配置xml路径
mapper-locations: "classpath:mybatis/mapper/*Mapper.xml"
#  配置model包路径
type-aliases-package: "com.redis.redis01.bean.*"#日志
logging.level.root=info
#logging.level.io.lettuce.core=debug
#logging.level.org.springframework.data.redis=debug#canal安装地址
canal.server=172.16.204.51:11111
canal.destination=example
#控制台刷新时间,每隔5秒检查一下数据库数据是否更新 根据需求设置其他时间
canal.timeout=5
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=canal
spring.datasource.password=canal
spring.datasource.url=jdbc:mysql://172.16.204.51:3306/redis?autoReconnect=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=CONVERT_TO_NULL&useSSL=false&serverTimezone=CTT&allowMultiQueries=true
package com.redis.redis01;import com.alibaba.otter.canal.client.CanalConnector;
import com.alibaba.otter.canal.client.CanalConnectors;
import com.alibaba.otter.canal.common.utils.AddressUtils;
import com.alibaba.otter.canal.protocol.Message;
import com.alibaba.otter.canal.client.CanalConnectors;
import com.alibaba.otter.canal.client.CanalConnector;
import com.alibaba.otter.canal.common.utils.AddressUtils;
import com.alibaba.otter.canal.protocol.Message;
import com.alibaba.otter.canal.protocol.CanalEntry.Column;
import com.alibaba.otter.canal.protocol.CanalEntry.Entry;
import com.alibaba.otter.canal.protocol.CanalEntry.EntryType;
import com.alibaba.otter.canal.protocol.CanalEntry.EventType;
import com.alibaba.otter.canal.protocol.CanalEntry.RowChange;
import com.alibaba.otter.canal.protocol.CanalEntry.RowData;
import com.google.gson.Gson;
import lombok.extern.slf4j.Slf4j;
import org.json.JSONException;
import org.json.JSONObject;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;import java.net.InetSocketAddress;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@Slf4j
public class CanalTest {public static final Integer _60SECONDS = 60;public static final String REDIS_IP_ADDR = "172.16.204.51";private  void redisInsert(List<Column> columns) throws JSONException {JSONObject jsonObject = new JSONObject();for (Column column : columns) {System.out.println(column.getName() + " : " + column.getValue() + "    update=" + column.getUpdated());jsonObject.put(column.getName(), column.getValue());}if (columns.size() > 0) {try (Jedis jedis = new RedisUtils().getJedis()){jedis.set(columns.get(0).getValue(), jsonObject.toString());} catch (Exception e) {e.printStackTrace();}}}public class RedisUtils {public  final String REDIS_IP_ADDR = "172.16.204.51";public  final String REDIS_pwd = "123456";public JedisPool jedisPool;public  Jedis getJedis() throws Exception {JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();jedisPoolConfig.setMaxTotal(20);jedisPoolConfig.setMaxIdle(10);jedisPool=new JedisPool(jedisPoolConfig, REDIS_IP_ADDR,6379,10000,REDIS_pwd);if (null != jedisPool) {return jedisPool.getResource();}throw new Exception("Jedispool is not ok");}}private void redisDelete(List<Column> columns) throws JSONException {JSONObject jsonObject = new JSONObject();for (Column column : columns) {jsonObject.put(column.getName(), column.getValue());}if (columns.size() > 0) {try (Jedis jedis = new RedisUtils().getJedis()) {jedis.del(columns.get(0).getValue());} catch (Exception e) {e.printStackTrace();}}}private void redisUpdate(List<Column> columns) throws JSONException {JSONObject jsonObject = new JSONObject();for (Column column : columns) {System.out.println(column.getName() + " : " + column.getValue() + "    update=" + column.getUpdated());jsonObject.put(column.getName(), column.getValue());}if (columns.size() > 0) {try (Jedis jedis =new RedisUtils().getJedis()){jedis.set(columns.get(0).getValue(), jsonObject.toString());System.out.println("---------update after: " + jedis.get(columns.get(0).getValue()));} catch (Exception e) {e.printStackTrace();}}}public  void printEntry(List<Entry> entrys) throws JSONException {for (Entry entry : entrys) {if (entry.getEntryType() == EntryType.TRANSACTIONBEGIN || entry.getEntryType() == EntryType.TRANSACTIONEND) {continue;}RowChange rowChage = null;try {//获取变更的row数据rowChage = RowChange.parseFrom(entry.getStoreValue());} catch (Exception e) {throw new RuntimeException("ERROR ## parser of eromanga-event has an error,data:" + entry.toString(), e);}//获取变动类型EventType eventType = rowChage.getEventType();System.out.println(String.format("================&gt; binlog[%s:%s] , name[%s,%s] , eventType : %s",entry.getHeader().getLogfileName(), entry.getHeader().getLogfileOffset(),entry.getHeader().getSchemaName(), entry.getHeader().getTableName(), eventType));for (RowData rowData : rowChage.getRowDatasList()) {if (eventType == EventType.INSERT) {redisInsert(rowData.getAfterColumnsList());} else if (eventType == EventType.DELETE) {redisDelete(rowData.getBeforeColumnsList());} else {//EventType.UPDATEredisUpdate(rowData.getAfterColumnsList());}}}}public static void main(String[] args) {System.out.println("---------O(∩_∩)O哈哈~ initCanal() main方法-----------");//=================================// 创建链接canal服务端CanalConnector connector = CanalConnectors.newSingleConnector(new InetSocketAddress(REDIS_IP_ADDR,11111), "example", "", "");  // 这里用户名和密码如果在这写了,会覆盖canal配置文件的账号密码,如果不填从配置文件中读int batchSize = 1000;//空闲空转计数器int emptyCount = 0;System.out.println("---------------------canal init OK,开始监听mysql变化------");try {connector.connect();//connector.subscribe(".*\\..*");connector.subscribe("redis.redis_syc");   // 设置监听哪个表connector.rollback();int totalEmptyCount = 10 * _60SECONDS;while (emptyCount < totalEmptyCount) {System.out.println("我是canal,每秒一次正在监听:" + UUID.randomUUID().toString());Message message = connector.getWithoutAck(batchSize); // 获取指定数量的数据long batchId = message.getId();int size = message.getEntries().size();if (batchId == -1 || size == 0) {emptyCount++;try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}} else {//计数器重新置零emptyCount = 0;new CanalTest().printEntry(message.getEntries());}connector.ack(batchId); // 提交确认// connector.rollback(batchId); // 处理失败, 回滚数据}System.out.println("已经监听了" + totalEmptyCount + "秒,无任何消息,请重启重试......");} catch (JSONException e) {throw new RuntimeException(e);} finally {connector.disconnect();}}}

截图
image.png

spingboot项目

        <dependency><groupId>top.javatool</groupId><artifactId>canal-spring-boot-starter</artifactId><version>1.2.1-RELEASE</version></dependency
# canal starter配置信息
canal.server=127.0.0.1:11111
canal.destination=examplelogging.level.root=info
logging.level.top.javatool.canal.client.client.AbstractCanalClient=error
package com.redis.redis01.canal;import com.redis.redis01.bean.RedisSyc;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import top.javatool.canal.client.annotation.CanalTable;
import top.javatool.canal.client.handler.EntryHandler;@Component
@CanalTable(value = "redis_syc")
@Slf4j
public class RedisCanalClientExample implements EntryHandler<RedisSyc> {@Overridepublic void insert(RedisSyc redisSyc) {EntryHandler.super.insert(redisSyc);log.info("新增 ---> {}",redisSyc);}@Overridepublic void update(RedisSyc before, RedisSyc after) {EntryHandler.super.update(before, after);log.info("更新前 --->{} , 更新后 --->{} ", before, after);}@Overridepublic void delete(RedisSyc redisSyc) {EntryHandler.super.delete(redisSyc);log.info("删除 --->{} " , redisSyc);}
}

image.png

私服监听

注意:canal依赖stater在中央仓库是不存在的,需要手动放进本地仓库或者你公司里面的nexus

	<!--canal依赖--><dependency><groupId>com.xpand</groupId><artifactId>starter-canal</artifactId><version>0.0.1-SNAPSHOT</version></dependency>
@SpringBootApplication
@EnableCanalClient
public class CanalApplication {public static void main(String[] args) {SpringApplication.run(CanalApplication.class,args);}
}
@CanalEventListener
public class CanalDataEventListener {/**** 增加数据监听* @param eventType* @param rowData*/@InsertListenPointpublic void onEventInsert(CanalEntry.EventType eventType, CanalEntry.RowData rowData) {rowData.getAfterColumnsList().forEach((c) -> System.out.println("By--Annotation: " + c.getName() + " ::   " + c.getValue()));}/**** 修改数据监听* @param rowData*/@UpdateListenPointpublic void onEventUpdate(CanalEntry.RowData rowData) {System.out.println("UpdateListenPoint");rowData.getAfterColumnsList().forEach((c) -> System.out.println("By--Annotation: " + c.getName() + " ::   " + c.getValue()));}/**** 删除数据监听* @param eventType*/@DeleteListenPointpublic void onEventDelete(CanalEntry.EventType eventType) {System.out.println("DeleteListenPoint");}/**** 自定义数据修改监听* @param eventType* @param rowData*/@ListenPoint(destination = "example", schema = "torlesse_test", table = {"tb_user", "tb_order"}, eventType = CanalEntry.EventType.UPDATE)public void onEventCustomUpdate(CanalEntry.EventType eventType, CanalEntry.RowData rowData) {System.err.println("DeleteListenPoint");rowData.getAfterColumnsList().forEach((c) -> System.out.println("By--Annotation: " + c.getName() + " ::   " + c.getValue()));}@ListenPoint(destination = "example",schema = "test_canal", //所要监听的数据库名table = {"tb_user"}, //所要监听的数据库表名eventType = {CanalEntry.EventType.UPDATE, CanalEntry.EventType.INSERT, CanalEntry.EventType.DELETE})public void onEventCustomUpdateForTbUser(CanalEntry.EventType eventType, CanalEntry.RowData rowData){getChangeValue(eventType,rowData);}public static void getChangeValue(CanalEntry.EventType eventType, CanalEntry.RowData rowData){if(eventType == CanalEntry.EventType.DELETE){rowData.getBeforeColumnsList().forEach(column -> {//获取删除前的数据System.out.println(column.getName() + " == " + column.getValue());});}else {rowData.getBeforeColumnsList().forEach(column -> {//打印改变前的字段名和值System.out.println(column.getName() + " == " + column.getValue());});rowData.getAfterColumnsList().forEach(column -> {//打印改变后的字段名和值System.out.println(column.getName() + " == " + column.getValue());});}}
}

案列四 统计千亿级别PV

UV: Unique Visitor ,独立访客数,是指在一个统计周期内,访问网站的人数之和。一般理解客户ip,需要去重
PV : Page View,浏览量,是指在一个统计周期内,浏览页面的数之和。不需要去重
DAU: Daily Active User 日活跃用户数量;去重
DNU:Daily New User,日新增用户数
MAU:Monthly New User,月活跃用户;去重
需要使用redis hyperloglog基数统计数据结构来实现
基数统计:数据集中不重复的元素的个数

模拟后台1万用户点击首页

package com.redis.redis01.service;import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StopWatch;
import javax.annotation.Resource;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;@Slf4j
@Service
public class HyperLogService {@Resourceprivate RedisTemplate<String, Object> redisTemplate;/*** 模拟后台1万用户点击首页,每个用户来自不同的ip地址*/public void hyperloglogUvTest() {StopWatch stopWatch=new StopWatch();stopWatch.start();CountDownLatch countDownLatch=new CountDownLatch(10000);//主子线程传递共享连接资源redisTemplateExecutorService executorService = Executors.newFixedThreadPool(200);executorService.execute(new Runnable() {@Overridepublic void run() {//模拟1万用户for (int i = 0; i < 10000; i++) {countDownLatch.countDown();Random random = new Random();String ipAddress = random.nextInt(256)+ "." + random.nextInt(256)+ "." + random.nextInt(256)+ "." + random.nextInt(256);redisTemplate.opsForHyperLogLog().add("uv_click", ipAddress);System.out.println("countDownLatch=" + countDownLatch.getCount());}}});try {countDownLatch.await();stopWatch.stop();Long uvClick1 = redisTemplate.opsForHyperLogLog().size("uv_click");//用户访问首页次数uv=10059System.out.println("用户访问首页次数uv=" + uvClick1);//共耗时=3:秒System.out.println("共耗时=" + stopWatch.getTotalTimeMillis()/1000+":秒");} catch (InterruptedException e) {throw new RuntimeException(e);}}
}

image.png

案列五 布隆过滤器方案实现

利用bitmap实现,一个bitmap=2^32bit最大能存512M,一个用户一天签到用1个bit,一年365个bit就可以实现,1千万个用户一年只需要435MB还不到一个bitmap最大存储能力
优点

  • 高效地插入和查询,内存占用 bit 空间少

缺点

  • 不能删除元素
    • 因为删除元素会导致误判率增加,因为hash冲突同一个位置可能存的东西是多个共有的,你删除一个元素的同时可能也把其他的删除了
  • 存在误判,不能精准过滤
    • 有,可能有
    • 无,绝对无
package com.redis.redis01.service;import com.google.common.collect.Lists;
import com.redis.redis01.bean.RedisBs;
import com.redis.redis01.mapper.RedisBsMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;import javax.annotation.Resource;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock;@Slf4j
@Service
public class BitmapService {@Resourceprivate RedisTemplate<String, Object> redisTemplate;private static ReentrantLock lock = new ReentrantLock();@Autowiredprivate RedisBsMapper redisBsMapper;/*** 场景一:布隆过滤器解决缓存穿透问题(null/黑客攻击);利用redis+bitmap实现* 有可能有,没有一定没有*                                                    无-------------》mysql查询*                     有--------》redis查询----------》有-----------》返回* 请求-----》布隆过滤器-----------》*                      无-------终止** @param type:0初始化,1常规查询*/public void booleanFilterBitmap(int type, Integer id) {switch (type) {case 0://初始化数据for (int i = 0; i < 10; i++) {RedisBs initBs = RedisBs.builder().id(i).name("赵三" + i).phone("1580080569" + i).build();//1 插入数据库redisBsMapper.insert(initBs);//2 插入redisredisTemplate.opsForValue().set("customer:info" + i, initBs);}//3 将用户id插入布隆过滤器中,作为白名单for (int i = 0; i < 10; i++) {String booleanKey = "customer:booleanFilter:" + i;//3.1 计算hashvalueint abs = Math.abs(booleanKey.hashCode());//3.2 通过abs和2的32次方取余,获得布隆过滤器/bitmap对应的下标坑位/indexlong index = (long) (abs % Math.pow(2, 32));log.info("坑位:{}", index);//3.3 设置redis里面的bitmap对应类型的白名单redisTemplate.opsForValue().setBit("whiteListCustomer", index, true);}break;case 1://常规查询//1 获取当前传过来的id对应的哈希值String inputBooleanKey = "customer:booleanFilter:" + id;int abs = Math.abs(inputBooleanKey.hashCode());long index = (long) (abs % Math.pow(2, 32));Boolean whiteListCustomer = redisTemplate.opsForValue().getBit("whiteListCustomer", index);//加入双检锁//加锁,大厂用,对于高qps的优化,进行加锁保证一个请求操作,让外面的redis等待一下,避免击穿mysqllock.lock();try {if (null == whiteListCustomer) {whiteListCustomer = redisTemplate.opsForValue().getBit("whiteListCustomer", index);if (null != whiteListCustomer && whiteListCustomer) {//布隆过滤器中存在,则可能存在//2 查找redisObject queryCustomer = redisTemplate.opsForValue().get("customer:info" + id);if (null != queryCustomer) {log.info("返回客户信息:{}", queryCustomer);break;} else {//3 redis没有查找mysqlRedisBs userById = redisBsMapper.findUserById(id);if (null != userById) {log.info("返回客户信息:{}", queryCustomer);redisTemplate.opsForValue().set("customer:info" + id, userById);break;} else {log.info("当前客户信息不存在:{}", id);break;}}} else {//redis没有,去mysql中查询//3 redis没有查找mysqlRedisBs userById = redisBsMapper.findUserById(id);if (null != userById) {log.info("返回客户信息:{}", userById);redisTemplate.opsForValue().set("customer:info" + id, userById);break;} else {log.info("当前客户信息不存在:{}", id);break;}}}} finally {lock.unlock();}log.info("当前客户信息不存在:{}", id);break;default:break;}}
}

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

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

相关文章

流体的压力

压力是流体力学中很重要的物理量&#xff0c;国际标准单位为 Pa&#xff08;帕斯卡&#xff09;&#xff0c;其他常用单位包括 MPa&#xff08;兆帕&#xff09;、atm&#xff08;标准大气压&#xff09;、Torr&#xff08;托&#xff09; 等。 在流体内部&#xff0c;压力是标…

数据结构树与二叉树的实现

目录 一、普通树的存储结构 1、双亲表示法 2.孩子表示法 二、二叉树 1.二叉树的顺序存储&#xff08;必须是完全二叉树&#xff0c;否则很浪费空间&#xff09; 1&#xff09;结构体 2.二叉树的链式存储 1&#xff09;结构体 2&#xff09;操作 1.创建一颗二叉树 2.创…

Linux内核mmap内存映射详解及例子实现

mmap在linux哪里&#xff1f; 什么是mmap&#xff1f; 上图说了&#xff0c;mmap是操作这些设备的一种方法&#xff0c;所谓操作设备&#xff0c;比如IO端口&#xff08;点亮一个LED&#xff09;、LCD控制器、磁盘控制器&#xff0c;实际上就是往设备的物理地址读写数据。 但…

【Zabbix监控二】之zabbix自定义监控内容案例(自动发现、自动注册)

一、自定义监控内容 案例&#xff1a;自定义监控客户端服务器登录的人数 需求&#xff1a;限制登录人数不超过3个人&#xff0c;超过5个人就发出报警 1、在客户端创建自定义key 明确需要执行的linux命令 创建zabbix监控项配置文件&#xff0c;用于自定义Key #在zabbix的配…

SMART PLC数值积分器功能块(矩形+梯形积分法完整源代码)

PLC的数值积分器算法也可以参考下面文章链接: PLC算法系列之数值积分器(Integrator)-CSDN博客文章浏览阅读1.5k次,点赞3次,收藏3次。数值积分和微分在工程上的重要意义不用多说,闭环控制的PID控制器就是积分和微分信号的应用。流量累加也会用到。有关积分运算在流量累加上…

算法-二叉树-简单-二叉树的遍历

记录一下算法题的学习6 首先我们要回忆一下怎么样遍历一个树&#xff1a; 三种遍历概念 先序遍历&#xff1a;先访问根节点&#xff0c;再访问左子树&#xff0c;最后访问右子树。 后序遍历&#xff1a;先左子树&#xff0c;再右子树&#xff0c;最后根节点。 中序遍历&…

kafka原理看这一篇就够了

为何使用消息队列 异步。接口方式实现多个系统协作&#xff0c;如图A系统作为用户请求接收方&#xff0c;需要调用多个系统的接口&#xff0c;这些接口还有可能是在A系统里同步调用&#xff0c;所以最后的接口耗时是多个系统接口耗时的总和&#xff1b;mq方式则可以异步发送消…

解决ubuntu23.10 wifi不能使用的问题

解决ubuntu23.10 wifi不能使用的问题 今天升级到了ubuntu23.10之后&#xff0c;wifi不能使用。 参考此视频解决了问题&#xff1a; https://www.youtube.com/watch?appdesktop&vn92O8rNKVb0 sudo lshw -class networkcd /etc/pm/sleep.dlssudo touch configsudo gedit co…

Java制作俄罗斯方块

Java俄罗斯方块小游戏 import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.util.ArrayList; import java.util.List; imp…

C#,怎么修改(VS)Visual Studio 2022支持的C#版本

一些文字来自于 Microsoft . &#xff08;只需要读下面的红色文字即可&#xff01;&#xff09; 1 C# 语言版本控制 最新的 C# 编译器根据项目的一个或多个目标框架确定默认语言版本。 Visual Studio 不提供用于更改值的 UI&#xff0c;但可以通过编辑 .csproj 文件来更改值。…

1688阿里巴巴官方开放平台API接口获取商品详情、商品规格信息列表、价格、宝贝详情数据调用示例说明

商品详情API接口在电商平台和购物应用中的作用非常重要。它提供了获取商品详细信息的能力&#xff0c;帮助用户了解和选择合适的商品&#xff0c;同时也支持开发者进行竞品分析、市场研究和推广营销等工作&#xff0c;以提高用户体验和促进销售增长。 1688.item_get-获得1688商…

单链表的实现(Single Linked List)---直接拿下!

单链表的实现&#xff08;Single Linked List&#xff09;—直接拿下&#xff01; 文章目录 单链表的实现&#xff08;Single Linked List&#xff09;---直接拿下&#xff01;一、单链表的模型二、代码实现&#xff0c;接口函数实现①初始化②打印链表③创建一个结点④尾插⑤尾…

Unity 场景烘培 ——unity Post-Processing后处理1(四)

提示&#xff1a;文章有错误的地方&#xff0c;还望诸位大神不吝指教&#xff01; 文章目录 前言一、Post-Processing是什么&#xff1f;二、安装使用Post-Processing1.安装Post-Processing2.使用Post-Processing&#xff08;1&#xff09;.添加Post-process Volume&#xff08…

Flutter 3.16 中带来的更新

Flutter 3.16 中带来的更新 目 录 1. 概述2. 框架更新2.1 Material 3 成为新默认2.2 支持 Material 3 动画2.3 TextScaler2.4 SelectionArea 更新2.5 MatrixTransition 动画2.6 滚动更新2.7 在编辑菜单中添加附加选项2.8 PaintPattern 添加到 flutter_test 3. 引擎更新&#xf…

文件隐藏 [极客大挑战 2019]Secret File1

打开题目 查看源代码发现有一个可疑的php 访问一下看看 点一下secret 得到如下页面 响应时间太短我们根本看不清什么东西&#xff0c;那我们尝试bp抓包一下看看 提示有个secr3t.php 访问一下 得到 我们看见了flag.php 访问一下可是什么都没有 那我们就进行代码审计 $file$_…

Servlet---上传文件

文章目录 上传文件的方法上传文件的示例前端代码示例后端代码示例 上传文件的方法 上传文件的示例 前端代码示例 <body><form action"upload" method"post" enctype"multipart/form-data"><input type"file" name&qu…

2023年中国地产SaaS分类、产业链及市场规模分析[图]

SaaS是一种基于云计算技术&#xff0c;通过订阅的方式向互联网向客户提供访问权限以获取计算资源的一项软件即服务。地产SaaS则是SaaS的具体应用&#xff0c;提供了一个线上平台&#xff0c;用于协助房地产供应商与购房者、建筑承建商、材料供应商及房地产资产管理公司之间的协…

【Linux网络】详解使用http和ftp搭建yum仓库,以及yum网络源优化

目录 一、回顾yum的原理 1.1yum简介 yum安装的底层原理&#xff1a; yum的好处&#xff1a; 二、学习yum的配置文件及命令 1、yum的配置文件 2、yum的相关命令详解 3、yum的命令相关案例 三、搭建yum仓库的方式 1、本地yum仓库建立 2、通过http搭建内网的yum仓库 3、…

Sentinel 热点规则 (ParamFlowRule)

Sentinel 是面向分布式、多语言异构化服务架构的流量治理组件&#xff0c;主要以流量为切入点&#xff0c;从流量路由、流量控制、流量整形、熔断降级、系统自适应过载保护、热点流量防护等多个维度来帮助开发者保障微服务的稳定性。 SpringbootDubboNacos 集成 Sentinel&…

Navicat for mysql 无法连接到虚拟机的linux系统下的mysql

原创/朱季谦 最近在linux Centos7版本的虚拟机上安装了一个MySql数据库&#xff0c;发现本地可以正常ping通虚拟机&#xff0c;但Navicat则无法正常连接到虚拟机里的MySql数据库&#xff0c;经过一番琢磨&#xff0c;发现解决这个问题的方式&#xff0c;很简单&#xff0c;总共…