首页
关于
Search
1
联想510s mini 安装 Ventura
901 阅读
2
基于K8s + Jenkins+Docker + Gitee 自动部署 - 配置 Jenkins Item + Gitee Webhook (二)
569 阅读
3
Spring Boot Schedule疑问及线程池配置
468 阅读
4
Server Send Events With Spring Boot
411 阅读
5
Ngrok使用自有服务器服务器及域名 - 解决Mac client问题
390 阅读
默认分类
SSH
typecho
Spring boot
其他
mysql
k8s
jenkins
docker
Java
mqtt
MongoDB
登录
/
注册
Search
标签搜索
k8s
docker
ssh
mysql
db
gitee
jenkins
ngrok
黑苹果
MQTT
CC
累计撰写
19
篇文章
累计收到
0
条评论
首页
栏目
默认分类
SSH
typecho
Spring boot
其他
mysql
k8s
jenkins
docker
Java
mqtt
MongoDB
页面
关于
搜索到
19
篇与
cc
的结果
2023-05-03
联想510s mini 安装 Ventura
背景使用联想天逸510S mini(十代 10400)安装黑苹果作为日常生产力工具。 本文参考的天逸510s Mini兼macOS BigSur安装教程,然后记录一些细节问题。配置:(仅贴出与官方配置有差异的地方)类型具体型号内存玖合(JUHOR) 48GB DDR4 3200 笔记本内存条PCIe梵想(FANXIANG)1TB SSD固态硬盘 M.2接口PCIe 4.0 x4SATA移速 金钱豹系列-256GB网卡博通BCM94360Z4准备// U盘 推荐 32 GB // Ventura 镜像 微信公众号 黑果小兵的部落阁 // Ventura 镜像刻录工具 balenaetcher https://www.balena.io/etcher // 小兵开源的EFI https://github.com/daliansky/Lenovo-TianYi-510S-Mini-Hackintosh安装修改Bios配置参考github中的信息.注:如果要关闭CPU的“CFG Lock”,需要先安装仓库中提供的BIOS版本,先进入Windows运行该程序完成BIOS版本替换(我先安装了Windows,然后进行替换,未在PE系统中测试。)制作启动U盘使用balenaetcher将下载的Ventura安装镜像刻录到U盘。 安装过程略。替换U盘中的EFI从小兵开源的EFI仓库Release页面下载最新EFI。 下载完成后,解压替换启动盘中的EFI目录。这一步必须操作,否则无法进入安装界面。注意:目前release中的版本是v2.3.0,从日志看不支持Ventura。 可以直接clone项目,然后手动拷贝EFI目录。[2023/5/3]分区将硬盘格式化为GUID分区,将在ESP分区中创建EFI文件夹,将U盘替换后的EFI拷贝进去,否则无法脱离U盘使用。 -- 这一步一定要在安装前做,实际测试安装后替换无效。安装插入U盘启动进入安装界面,选择“Install macOS Ventura”进行安装:等待一会,顺利的话会进入安装界面:首先选择“磁盘工具-Disk Utility”格式化硬盘格式为“APFS”,如果格式化失败,先格式化为“Mac OS Extended (Hournaled)”,再格式化为“APFS”.接下来退出Disk Utility,选择“Install macOs Ventura”进行安装,选中格式化好的磁盘,进行安装。安装过程会几次重启,等待即可。完成顺利的话会进入配置界面,创建用户等操作,后续即可拔掉U盘使用。总结安装过程折腾了好几天,不过好在有小兵这样的大神提供完整的EFI,减少了很多折腾的时间。非常感谢!这个机器是20年发布的到现在已三年左右,CPU已是前三代的产品。不过作为日常写写代码已满足使用,比起二手的Mac迷你仍有不小的性价比。再加上目前内存、硬盘价格非常划算,300块可买32GB内存条。 比起苹果升级太香了!这个机器购买起来比较折腾,先在淘宝买的“准系统”,然后自购CPU、内存、硬盘及网卡,整个过程还是比较有意思。目前使用下来还体验还不错,Wi-Fi、蓝牙、接力等等都可正常使用,比目前使用的15款的Macbook Pro体验好得多。说一说问题:1、使用Ipad Pro当扩展屏,拖动的时候界面不会刷新,当操作完成后才用响应。2、在SATA固态上安装的Win11,目前开机后Core0会占用100%,解决方案是拔掉所有USB,等待一会再插上去即可恢复。3、网卡天线的安装对网速、蓝牙影响,如果自购准系统,建议让卖家帮忙安装天线。其他如果你折腾这个设备遇到了问题,可以联系我提供一些帮助(如果你啥经验都没有,建议直接买成品吧)。联系我:Email: idkpengc@gmail.com文章底部留言。引用https://blog.daliansky.net/Lenovo-Tianyi-510s-Mini-and-macOS-BigSur-Installation-Tutorial.html https://github.com/daliansky/Lenovo-TianYi-510S-Mini-Hackintosh
2023年05月03日
901 阅读
0 评论
0 点赞
2023-04-02
Server Send Events With Spring Boot
背景在使用ChatGPT时,通过http.Post进行调用耗时非常高,体验较差。 OpenAI提供了Stream(即SSE)的返回模式,不用等到响应生成完后才给到客户端响应。使用Stream API方式时,那么后端与前端也需要使用SSE的方式进行API交互。源代码在文末。Server-Sent Events(SSE)是一种在Web浏览器和服务器之间实现服务器推送事件的技术。它允许服务器向客户端推送事件流,这些事件可以是简单的文本消息或具有自定义格式的复杂数据。SSE 是一种基于 HTTP 的协议,使用长轮询或 HTTP 流来实现服务器推送。 SSE 可以用于各种用例,例如实时更新股票报价、即时聊天应用程序、新闻或社交媒体网站上的实时更新等等。与传统的轮询或 WebSocket 不同,SSE 不需要客户端发起请求,而是服务器始终保持连接打开并推送数据,因此 SSE 可以更高效地使用网络带宽和服务器资源。 在前端,可以使用 JavaScript API 来监听服务器发送的事件,以及处理和显示这些事件的数据。 引用:## open ai chat api https://platform.openai.com/docs/api-reference/chat/create ## Request ChatGPT API Using Spring boot https://www.taogenjia.com/2023/03/16/The-Guide-to-Call-ChatGPT-3-5-Event-Stream-API-in-Spring-Boot/ ## Doc https://developer.mozilla.org/en-US/docs/web/api/server-sent_events/using_server-sent_events https://developer.mozilla.org/zh-CN/docs/Web/API/EventSource ## nginx https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_buffering理解常见的HTTP请求是服务端把命令执行完成过后给到一个结果,那么客户端必须等待,大部分场景下这是没问题的。 那么在处理时间比较高的场景下,客户端可以实时接收部分处理结果,增加用户体验。我们从另一个角度看Http,HttpServerRequest: 有输入流,包含请求的数据HttpServletResponse:有输出流,可以写响应数据。那么我们处理Http请求时,不断的向Response的 outputStream写入数据,那么在没有处理完成响应的情况下也可以收到一部分数据。 @GetMapping("test") public void test(HttpServletRequest request, HttpServletResponse response) throws Exception { // 设置响应的Content-Type为text/event-stream response.setContentType("text/event-stream"); response.setStatus(200); OutputStream outputStream = response.getOutputStream(); int i = 10; while (i >= 0) { String message = "i : " + i + "\n"; outputStream.write(message.getBytes(StandardCharsets.UTF_8)); outputStream.flush(); try { Thread.sleep(1000); } catch (Exception ignored) {} i--; } }我们打开浏览器访问这个api时,界面会间隔一秒输出一行:i : 10 i : 9 i : 8 i : 7 i : 6 i : 5 i : 4 i : 3 i : 2 i : 1 i : 0点击查看效果有了以上理解,我们只需要按着SSE要求的格式不停的向写入数据即可。SSE 格式1、id event dataid: xxx \n event: message \n data: hello from cc\n\n2、event dataevent: message \n data: hello from cc\n\n3、data onlydata: hello from cc\n\n其中: event: 事件名称,可以自己定义,默认是message id、event: 可选4、结束推送 如果使用sse,需要服务端进行关闭,如果不关闭前端会不停的重试(retry)自定义事件event: close \n data:\n\n前端订阅然后close,然后关闭sse const sse = new EventSource('/api/sse'); /* * 自定义订阅 */ sse.addEventListener("close", function(e) { sse.close(); }) /* This will listen only for events * similar to the following: * * event: notice * data: useful data * id: someid * */ sse.addEventListener("notice", function(e) { console.log(e.data) }) /* Similarly, this will listen for events * with the field `event: update` */ sse.addEventListener("update", function(e) { console.log(e.data) }) /* The event "message" is a special case, as it * will capture events without an event field * as well as events that have the specific type * `event: message` It will not trigger on any * other event type. */ sse.addEventListener("message", function(e) { console.log(e.data) })发送特殊标记的datadata: [DONE]\n\n处理 '[DONE]'的message const sse = new EventSource('/api/sse'); /* The event "message" is a special case, as it * will capture events without an event field * as well as events that have the specific type * `event: message` It will not trigger on any * other event type. */ sse.addEventListener("message", function(e) { var data = e.data; if (data.contains('[DONE]')) { sse.close() } else { console.log(data) } })踩坑1、使用Nginx部署后,消息没有一个一个收到,而是在处理完成后整个返回修改 nginxlocation / { proxy_pass http://127.0.0.1:xxx/; ... # When buffering is disabled, the response is passed to a client synchronously, immediately as # it is received. nginx will not try to read the whole response from the proxied server. The # maximum size of the data that nginx can receive from the server at a time is set by the # proxy_buffer_size directive. proxy_buffering off; ... }2、如果使用了WebFiler,需要开启asyncSupported @WebFilter(urlPatterns = "*",asyncSupported = true) public class SomeFilter implements Filter { .... }源代码https://gitee.com/pchenc/sse-demo
2023年04月02日
411 阅读
0 评论
1 点赞
2023-03-27
摘自-《幸福之路》
“我想我能变成动物,和它们为伴,它们是那么恬静那么矜持,我站着,久久的望着它们。它们不为儿女作牛马,也不为几女哀号,它们不在黑暗里睁眼失眠,为了它们的罪过啼泣,它们不喋喋讨论对上帝的责任,使我头晕脑胀,没有一个不满,没有一个为了占有欲而癲狂,没有一个向另一个屈膝,也不对几千年前的袓先跪拜,在整个的地球上没有一个有什么身份,也没有一个忧郁哀伤。——惠特曼Walt Whitman”摘录来自幸福之路罗素此材料可能受版权保护。“而过事诛求的结果,一定使你连应得的一份都落空。一个人能凭藉一些真正的兴趣,例如德朗会议或星辰史等,而忘记他的烦虑的话,当他从无人格的世界上旅行回来时,定将发觉自己觅得了均衡与宁静,使他能用最高明的手段去对付他的烦虑,而同时也尝到了真正的、即使是暂时的幸福。 幸福底秘诀是:让你的兴趣尽量的扩大,让你对人对物的反应,尽量的倾向于友善。”摘录来自: 罗素. “幸福之路 (傅雷经典译文全集).” Apple Books. 此材料受版权保护。
2023年03月27日
200 阅读
0 评论
0 点赞
2023-03-04
我是怎么样优化API的?
背景在系统开发过程中,以迭代功能为主,在使用过程中遇到性能瓶颈再进行针对性优化。本次遇到的问题是在导入3000+FAQ时导入时间大概500S左右,用户端提示导入失败,结果数据在导入中。文章出现的代码经过处理,不包含任何具体隐私信息,仅还原场景概念:FAQ:一般指常见问题解答。常见问题解答(frequently-asked questions,简称FAQ)即:一个问题对应一个答案。他的结构大概是一组相关的问题和一个答案。比如“你是谁?”这个问题,他会有不同的问法:“你叫什么?你叫啥?”,然后对应一个答案:"我是xxx"意图/Intent:即FAQ中的“你是谁”目标1、在3000条数据时,导入在时间优到5秒内(而用户能够忍受的最长等待时间在6~8秒之间);2、优化查询,返回时间在1S内;步骤1、找出瓶颈点;2、确定优化措施;3、实施;这三个步骤看似简单,其实需要在开发过程中逐步积累出自己的套路及经验。FAQ批量导入这里从系统csv文件中读取出意图和答案,再将数据保存到数据库,然后做一些检查,把错误的信息打上标签。解析数据保存校验、更新找出瓶颈点首先,我们发现API慢时,我们要知道哪些步骤慢。 这里我们用到google的Stopwatch进行“打桩”,给个操作计时。如 public static void main(String[] args) { // 创建后可多次输出耗时 Stopwatch stopwatch = Stopwatch.createStarted(); ThreadUtil.sleep(1000); System.out.println("消耗时间:" + stopwatch.elapsed(TimeUnit.MILLISECONDS)); ThreadUtil.sleep(2000); System.out.println("消耗时间:" + stopwatch.elapsed(TimeUnit.MILLISECONDS)); } 消耗时间:1001 消耗时间:3009回到我们的导入FAQ代码 public void save(String faqId, MultipartFile file) throws Exception { Stopwatch stopwatch = Stopwatch.createStarted(); // 1、从faq文件解析出数据 List<List<String>> rows = _readRows(file); LOG.info("[import faq - finish check used:]", stopwatch.elapsed(TimeUnit.MILLISECONDS)); // 获取一个链接 Connection conn = getConn(); // 一个FAQ由一个User、一个Bot组成 // 转换成faq pair 对 List<Pair<User, Bot>> details = _convertRows(rows, faqId); LOG.info("[import faq - convert: ]", stopwatch.elapsed(TimeUnit.MILLISECONDS)); // 保存到数据库 for (Pair<User, Bot> p : details) { ComponentDao.add(conn, p.getKey().toComponent()); ComponentDao.add(conn, p.getRight().toComponent()); } LOG.info("[import faq - save 2 db: ]", stopwatch.elapsed(TimeUnit.MILLISECONDS)); conn.commit(); LOG.info("[import faq - finish commit 2: ]", stopwatch.elapsed(TimeUnit.MILLISECONDS)); _validate(faqId, conn); conn.commit(); } private void _validate(String faqId, Connection conn, String dbName) throws Exception { Stopwatch stopwatch = Stopwatch.createStarted(); // 1、创建一个上下文,携带一些信息 ComponentValidatorContext context = _buildContext(componentId, conn); LOG.debug("[validate faq - build context:{}]", stopwatch.elapsed(TimeUnit.MILLISECONDS)); // 2、 query 当前节点所在的validator,将user、bot转换成校验器 Pair<ComponentValidator /*Faq Validator*/, List<ComponentValidator>/*children validators*/> validatorPair = _validators(context); LOG.debug("[validate faq - build validate pair:{}]", stopwatch.elapsed(TimeUnit.MILLISECONDS)); // 3、遍历节点,更新错误信息 LOG.debug("[validate faq - start validate with length:{}, spend:{}]", validators.size(), stopwatch.elapsed(TimeUnit.MILLISECONDS)); int writeDbCount = 0; boolean hasError = false; for (ComponentValidator validator : validators) { StatusCodes statusCodes = validator.validate(context); if (statusCodes != null) { hasError = true; } // 节点错误 boolean updated = updateComponentError(validator); if (updated) { ComponentDao.update(conn, validator.getComponent()); writeDbCount++; } } LOG.debug("[validate faq - finish validate with write 2 db count:{}, spend:{}]", writeDbCount, stopwatch.elapsed(TimeUnit.MILLISECONDS)); // 3、更新FAQ上的标记 _updateError(faqId, hasError); LOG.debug("[validate used in millis:{}]", stopwatch.elapsed(TimeUnit.MILLISECONDS)); }运行结果[import faq - finish check used:147] [import faq - convert:241] [import faq - finish save 2 db:50870] [import faq - finish commit:50878] [start validate] [validate faq - build context:33486] [validate faq - build validate pair:35203] [validate faq - start validate with length:6795, spend:35203] [validate faq - finish validate with write 2 db count:6795, spend:361809] [validate used in millis:361818]到此,我们可以从打桩信息中得出瓶颈:保存到数据库,花费50s+构建上下文:33s+检验所有节点:326s+确定优化措施1、保存到数据库,花费50s+代码: // 保存到数据库 for (Pair<User, Bot> p : details) { ComponentDao.add(conn, p.getKey().toComponent()); ComponentDao.add(conn, p.getRight().toComponent()); }这里有3397个FAQ,6794个User、Bot节点;那么就执行6794次save操作。方案:结合以往经验,这里我们做批量保存。减少db写入次数, 批量写入;mysql批量写入优化;1、改为批量写入 : 写入db次数变成了 14次private void _saveBach(List<Pair<RestProjectComponentUser, RestProjectComponentBot>> data, Connection conn, String dbName)throws SQLException { String sql = "INSERT INTO tableName (id, xx, xx) VALUES (?, ?, ?) "; PreparedStatement ps = conn.prepareStatement(sql); for (int i = 0; i < data.size(); i++) { Pair<User, Bot> item = data.get(i); Component user = item.getKey().toComponent(); Component bot = item.getValue().toComponent(); _add2Bach(ps, user); _add2Bach(ps, bot); // 每500次提交一下 if (i % 500 == 0) { ps.executeBatch(); conn.commit(); ps.clearBatch(); } } ps.executeBatch(); conn.commit(); ps.clearBatch(); } private void _add2Bach(PreparedStatement ps, Component component) throws SQLException{ ps.setString(1, component.getId()); ps.setString(2, component.getXX()); ps.setString(3, component.getXx()); ps.addBatch(); } 2、mysql批量写入优化修改链接信息,重写批量写入jdbc:mysql://127.0.0.1:3305?rewriteBatchedStatements=true验证## 时间从50S+到1s+ [import faq - finish save 2 db:1586]总结: 这里原来每次保存都要写入数据库,大的数据量下这效率必然是低下的。经过批量保存和重写批量insert的sql,这减少了写入db次数,提高了sql批量写入效率# 之前:6794次insert insert into xx (xx,xx) values (xx,xx) # 之后:14次批量insert INSERT INTO xx (xx,xx) values ('xxx','xxx'),('xxx','xxx')...2、构建上下文:33s+针对构建上下文,我们在再次打桩,查找瓶颈点[build - context start build validate context] [build - convert2Rest - start] [build - convert2Rest - find root & query all projects spend:652] [build - convert2Rest - load children:10113] [build - convert2Rest - covert children:22067] [build - build - context convert2Rest pair spend:22072] [build - build - context spend:32382]这里大概的逻辑:查询顶级节点、查询当前项目下所有的节点信息还原关联关系从db对象转换成Rest对象筛选一些节点优化的点多次查询当前项目的所有节点-这数据量大,多次重复操作这在只查询一次,其他方法用到所有节点时通过参数传入还原关联关系这用了递归,实现简单数据量大时效率不如 while /for 循环 -- 改为while + for从db对象转换成Rest对象-在循环中查询整个项目数据减少重复查询db循环中使用stream,筛选/转换少量 - 这种场景下效率不如for筛选一些节点减少db查询递归转while /for验证[build - context start build validate context] [build - convert2Rest - start] [build - convert2Rest - find root & query all projects spend:3] [build - convert2Rest - load children:15] [build - convert2Rest - covert children:124] [build - context convert2Rest pair spend:128] [build - context spend:214]总结:减少db查询合理使用stream递归转while / for实现这优化还是很明显,从32S+到0.2S。3、检验所有节点:326s+这是最耗时的地方,先分析下代码:for循环所有节点,执行校验逻辑 - 验证意图(Intent,用户节点的输入需要唯一)不能重复如果有错误,将错误信息更新并同步数据库finish validate with write 2 db count:6795, spend:361809这提示总共写了6795次db,这显然不合理,因为在测试的时候是全新的数据,不会重复。 int writeDbCount = 0; boolean hasError = false; for (ComponentValidator validator : validators) { StatusCodes statusCodes = validator.validate(context); if (statusCodes != null) { hasError = true; } // 节点错误 boolean updated = updateComponentError(validator); if (updated) { ComponentDao.update(conn, validator.getComponent()); writeDbCount++; } }这里结合上面的套路优化stream修复更新db的bugfinish validate with write 2 db count:0, spend:108427 这总时间从361s+到108s+,提升非常大。剩余的代码检查没有操作db,但仍然有如此高的耗时,这很不合理。那么108427 / 6794 约等于16毫秒,校验一个节点需要这么久,显然不正常。我们再次使用Stopwatch打点状,输出下校验的耗时 for (ComponentValidator validator : validators) { Stopwatch sw = Stopwatch.createStarted(); StatusCodes statusCodes = validator.validate(context); if (statusCodes != null) { hasError = true; } long usedInMillis = sw.elapsed(TimeUnit.MILLISECONDS); if (usedInMillis > 20) { LOG.debug("[validate usedInMillis:{}]", usedInMillis) } .... }... [validate component usedInMillis:199] ...这就发现了问题,部分节点耗时异常的高。排查后发现这些节点的intent数量很大,intent越大越耗时。 // 经过排查发现, 在校验User.Intent时会判断是否与数据库已存在的意图重复。 // 那么这里有很大的计算量: // 这样判断扩展问重复从 example.size * db.example.size private boolean intentRepeated() { List<String> exampleTexts = getIntents(); //user的example不能重复,如果是引用的摸板则允许重复 List<User> users = componets.stream() // filter .collect(Collectors.toList()); for (User item : users) { List<String> examples =item.getIntents(); for (String text : examples) { if (exampleTexts.contains(text)) { return true; } } } return false; } 我们来看看这里的问题:循环中有stream, 这部分在筛选db中的User这里所有节点都是一样的,不用每次都计算,将结果在context中缓存起来,只计算一次,后续直接用for * for计算量大修改实现1、User从context获取,不在这进行计算 - 计算次数减少了6000次+ List<User> users= null; public List<User> getUsers() { if (users == null) { synchronized (this) { if (users == null) { users = componets.stream() // ... filter .collect(Collectors.toList()); } } } return users; }2、减少计算次数这里需要一定的经验积累,比如在第二个for循环中,我们改成map.containsKey去判断,就去掉了一个for循环。Tip:在最好的情况下,map.containsKey(key)的时间复杂度时o(1)Context中构建db中存在的Intents,只在第一次用到时计算,其他后续节点直接使用即可; private boolean intentRepeated() { List<String> exampleTexts = getIntents(); Map<String> exitsIntents = context.intentsMap(); for (String text : examples) { if (exitsIntents.containsKey(text)) { return true; } } return false; }这样以来,我们的计算量变:从 example.size db.example.size 变成 example.size 1 次;验证:[start validate with length:6795] [finish validate with write 2 db count:0, spend:227]总结:合理使用stream缓存结果,减少计算次数减少计算次数结论至此,完成了本次优化,同样数据导入时间控制在了5s左右。减少db读写次数、批量操作;合理使用stream,在循环且单个集合数量较小的情况下,使用循环;合理使用递归修复Bug合适使用Context,将结果缓存,减少重复计算减少计算量经过此次调整,其中重要的是如何发现问题,解决方案可以有很多。同时要知道调整后,多少是符合预期的,不能盯着小的优化点去弄,这种优化空间太低了,反而会花很多时间效果也不会好。如何找到瓶颈点预期优化效果在开发过程中,我们还是以满足实现为主,在时间可能的情况下合理保证代码效率,可以在后续碰到瓶颈时单独处理即可。打个广告:如果你是RASA开发者: PromptAI支持通过脑图的方式编辑对话流程,编辑完成后可一键下载Rasa Project文件,可在原生Rasa直接训练[部分高级功能需要配合应用使用] - 功能: FAQ、多轮、变量提取(单个/多个)、form、webhook及自定义Action等高级功能; - 编辑: 支持拖拽、回收站、收藏、撤销、重做及复制等便捷功能; - 调试: 编辑完成后可一键调试、发布,最大程度减少开发时间。我们提供高性能GPU(3090),让你的效率起飞! - 其他: 支持私有部署,如果你有需求可联系我们! 如果你是普通用户,不会开发,但是想用AI对话: PromptAI对普通用户/企业极度友好,无需了解编程/AI的相关知识,也可快速上手,比Excel使用还简单,没有复杂繁琐的操作及公式! 几分钟就能拥有自己的ChatBot! 免费使用:https://www.promptai.cn 注册过程:免费试用 -> 填写试用信息 -> 审核通过 -> 即可上手!
2023年03月04日
136 阅读
0 评论
0 点赞
2022-12-10
Frp 内网穿透
开源内网穿透工具
2022年12月10日
358 阅读
0 评论
0 点赞
1
2
3
4